RecyclerView完全解析(二)——打造新版类Gallery效果

特别声明:

一、前言

  • 话说RecyclerView已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明RecyclerView拥有比ListView,GridView之类控件有很多的优点,例如:数据绑定,Item View创建,View的回收以及重用等机制。那么今天开始我们来重点学习一下RecyclerView控件,本系列文章会包括到以下三个部分:

1. RecyclerView控件的基本使用,包括基础,进阶,高级部分,动画之类
2. RecyclerView控件的实战实例
3. RecyclerView控件集合AA(Android Annotations)注入框架实例

  • 今天使我们本系列文章的第二讲主要是我们通过RecyclerView来打造一个新版类似Gallery控件的效果。本次讲解所有用的Demo例子已经全部更新到下面的项目中了,欢迎大家star和fork。

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

二、基本实现

  • 上一讲我们已经对于RecyclerView的基本使用和进阶部分做了讲解(点击进入),下面我们一步步的来打造一个新版Gallery效果控件。

  • 先来看一下和RecyclerView相关类:

类名 说明
RecyclerView.Adapter 可以托管数据集合,为每一项Item创建视图并且绑定数据
RecyclerView.ViewHolder 承载Item视图的子布局
RecyclerView.LayoutManager 负责Item视图的布局的显示管理
RecyclerView.ItemDecoration 给每一项Item视图添加子View,可以进行画分隔线之类的东西
RecyclerView.ItemAnimator 负责处理数据添加或者删除时候的动画效果
  • 那如果要实现Gallery的效果,里面的Item是横向滑动的,也就是说我们的RecyclerView可以支持横向滑动,这边我们直接采用了LinearLayoutManager布局管理器,同时设置方向为:HORIZONTAL(水平)
下面来具体看代码:

1、作为RecyclerView控件,我们需要设置每一项Item的布局:

<?xmlversionxmlversion="1.0" encoding="utf-8"?>  
<LinearLayoutxmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_height="wrap_content"  
    android:layout_width="wrap_content"  
    android:gravity="center"  
    android:padding="8.0dip">  

    <ImageView  
        android:id="@+id/item_img"  
        android:layout_width="100dp"  
        android:layout_height="100dp"  
        android:scaleType="fitXY"  
        android:adjustViewBounds="true"  
        android:src="@drawable/ic_item_gallery"/>  
    <TextView  
        android:id="@+id/item_tv"  
        android:text="标题1"  
        android:layout_marginTop="5dp"  
        android:textSize="15sp" 
        android:layout_gravity="center_horizontal"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        />  
</LinearLayout>  
  • 这个布局中我们比较简单,定义了一个图片和一个标题,垂直方向布局。

2、间接着,和ListView写法差不多,需要自定义适配器,来创建每一项布局视图以及把数据和视图绑定起来,所以这边继承RecyclerView.Adapter类创建一个自定义适配器GalleryRecyclerAdapter.java

  • 那么需要实现基类中的三个方法:
  • onCreateViewHolder(ViewGroup parent,int viewType) :创建Item View然后通过ViewHolder来承载
  • onBindViewHolder(ViewHolder holder,int position):进行视图和数据绑定

  • getItemCount():获取列表中视图Item的数量

  • 具体GallerRecyclerAdapter实现代码如下:

public class GalleryRecyclerAdapter extends RecyclerView.Adapter<GalleryRecyclerAdapter.ViewHolder> {  
   
    private List<GalleryModel> models;  
    private LayoutInflater mInflater;  
   
    public GalleryRecyclerAdapter(Context context){  
        models=new ArrayList<GalleryModel>();  
        for (int i=0;i<20;i++){  
            int index=i+1;  
            models.add(new GalleryModel(R.drawable.ic_item_gallery,"Item"+index));  
        }  
        mInflater=LayoutInflater.from(context);  
    }  
   
    /** 
     * 创建Item View  然后使用ViewHolder来进行承载 
     * @param parent 
     * @param viewType 
     * @return 
     */  
    @Override  
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
        View view=mInflater.inflate(R.layout.item_gallery_recycler,parent,false);  
        ViewHolder viewHolder=new ViewHolder(view);  
        return viewHolder;  
    }  
   
    /** 
     * 进行绑定数据 
     * @param holder 
     * @param position 
     */  
    @Override  
    public void onBindViewHolder(ViewHolder holder, int position) {  
       holder.item_img.setImageResource(models.get(position).getImgurl());  
       holder.item_tv.setText(models.get(position).getTitle());  
    }  
   
    @Override  
    public int getItemCount() {  
        return models.size();  
    }  
   
   
    //自定义的ViewHolder,持有每个Item的的所有界面元素  
    public static class ViewHolder extends RecyclerView.ViewHolder {  
        private ImageView item_img;  
        private TextView item_tv;  
        public ViewHolder(View view){  
            super(view);  
           item_img=(ImageView)view.findViewById(R.id.item_img);  
           item_tv=(TextView)view.findViewById(R.id.item_tv);  
        }  
    }  
   
}  

3、注意看上面的代码,我们继承了RecyclerView.ViewHolder实现一个自定义类ViewHolder,这个用来承载我们的子Item视图,现在Google已经要求开发者必须要使用ViewHolder了。在ViewHolder中我们进行控件的初始化工作,然后保存View视图。

   //自定义的ViewHolder,持有每个Item的的所有界面元素  
    public static class ViewHolder extends RecyclerView.ViewHolder {  
        private ImageView item_img;  
        private TextView item_tv;  
        public ViewHolder(View view){  
            super(view);  
           item_img=(ImageView)view.findViewById(R.id.item_img);  
           item_tv=(TextView)view.findViewById(R.id.item_tv);  
        }  
    }  

4、最后在Activity中控件设置,例如布局管理器,Adapter绑定即可,完整代码如下:

public class RecyclerGalleryActivity extends BaseActivity {  
    private RecyclerView gallery_recycler;  

    @Override  
    protected void onCreate(BundlesavedInstanceState) {  
        super.onCreate(savedInstanceState);  
       setContentView(R.layout.recycler_gallery_layout);  
       
        //初始化RecyclerView控件  
        gallery_recycler=(RecyclerView)this.findViewById(R.id.gallery_recycler);  
        //固定高度  
        gallery_recycler.setHasFixedSize(true);  
        //创建布局管理器  
        LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this);  
        //设置横向  
        linearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);  
        //设置布局管理器  
        gallery_recycler.setLayoutManager(linearLayoutManager);  
        //创建适配器  
        GalleryRecyclerAdapter adapter=new GalleryRecyclerAdapter(this);  
        //绑定适配器  
        gallery_recycler.setAdapter(adapter);  
    }  
    class CustomOnClickListener implements View.OnClickListener{  
        @Override  
        public void onClick(View v) {  
           RecyclerGalleryActivity.this.finish();  
        }  
    }  
}  

5、在看运行效果之前,我们先来看下上面的代码,上面的代码基本注释已经全部加了,相应大家可以看的懂,不过我们需要来讲一下上面的LayoutManager(布局管理器)。

  • 在上一讲中我们也讲到了,LayoutManger(布局管理器)该类负责将每一个Item视图在RecyclerView中的布局。目前RecyclerView已经给我们提供三个内置管理器:

LinearLayoutManger
GridLayoutManger
StaggeredGridLayoutManager

  • 这边的例子中我们是采用LinearLayoutManger而且设置了横向水平布局了。当然LinearLayoutManger还给我们提供了以下几个方法来让开发者方便的获取到屏幕上面的顶部item和顶部item相关的信息:

1、findFirstVisibleItemPosition()
2、findFirstCompletlyVisibleItemPosition()
3、findLastVisibleItemPosition()
4、findLastCompletlyVisibleItemPosition()

  • 这边的具体设置代码如下:
//创建布局管理器  
LinearLayoutManagerlinearLayoutManager=new LinearLayoutManager(this);  
//设置横向  
inearLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);  
//设置布局管理器  
allery_recycler.setLayoutManager(linearLayoutManager);  

6、初步运行效果如下:

三、升级加入点击事件

  • 通过上面的方式我们显示了一个类似于Gallery的效果,但是还远远不如实际Gallery的效果,现在只是可以有多项Item以及可以左右滑动,但是没有点击事件,下面我们来加入点击事件操作。

  • 对于ListView来讲,我们可以为ListView加入setOnItemClickListener监听事件,但是对于RecyclerView控件来讲,RecyclerView已经不再负载Item视图的布局和显示,这些工作已经交给了LayoutManger来做了。所以RecyclerView也没有给我们提供类似onItemClick事件,这样如果非得要实现类似的功能,我们开发者也可以自定义模拟实现。来,我们继续往下看….

1、我们最终要实现点击列表上面每一项Item来回调点击方法,那么我们可以在Adapter中的每一项View上面做文章,首先我们来看一下Adapter中的onCreateViewHolder()方法:

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
       Viewview=mInflater.inflate(R.layout.item_gallery_recycler,parent,false);  
       ViewHolder viewHolder=new ViewHolder(view);  
       return viewHolder;  
   }  

2、该方法创建出了Item 视图,然后通过ViewHolder来进行承载了,既然这样那我们可以在View加载出来之后给它设置一些属性例如:颜色,大小,当然也可以是点击事件等等。那这边我们给View添加onClick事件,然后在onClick方法把View点击触发的事件回调出去,同时可以回调一些参数内容出去。OK,那么我们这边就需要一个自定义的接口了,我们创建一个GallerRecyclerAdapter的内部类接口:
注意:内部类接口

   /** 
     * 类似ListView的 onItemClickListener接口 
     */  
    public interface OnRecyclerViewItemClickListener{  
        /** 
         * Item View发生点击回调的方法 
         * @param view   点击的View 
         * @paramposition  具体Item View的索引 
         */  
        void onItemClick(View view,intposition);  
    }  

3、然后定义接口,同时提供set和get方法,来让外部传入该接口,初始化:

    private OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;  
   
    public OnRecyclerViewItemClickListener getOnRecyclerViewItemClickListener() {  
        return onRecyclerViewItemClickListener;  
    }  
    public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) {  
        this.onRecyclerViewItemClickListener =onRecyclerViewItemClickListener;  
    }  

4、现在开始在onCreateViewHolder()方法中给View添加一个onClick事件,然后相应处理,判断onRecyclerViewItemClickListener是否存在,把事件回调出去:

view.setOnClickListener(newView.OnClickListener() {  
           @Override  
           public void onClick(View v) {  
              if(onRecyclerViewItemClickListener!=null){  
                  onRecyclerViewItemClickListener.onItemClick(view,(int)view.getTag());  
               }  
           }  
 });  

5、上面的代码中大家可能注意到onItemClick()方法中的第二个参数,获取了tag,因为这边的position索引值是在onBindViewHolder()方法中设置的:

public void onBindViewHolder(ViewHolder holder, int position) {  
       holder.item_img.setImageResource(models.get(position).getImgurl());  
       holder.item_tv.setText(models.get(position).getTitle());  
       holder.itemView.setTag(position);  
    }  

6、OK这边我们搞定了一个Item点击监听方法,接下去就是使用了:

adapter.setOnRecyclerViewItemClickListener(new GalleryRecyclerAdapter.OnRecyclerViewItemClickListener() {  
            @Override  
            public void onItemClick(View view,int position) {  
               Toast.makeText(RecyclerGalleryActivity.this,"您点击的Item的索引为:"+position,Toast.LENGTH_SHORT).show();  
            }  
        });  

7、现在该功能代码整完了,运行效果如下:

四、升级之加入分割线

  • 上面我们已经给每一项Item加入了点击回调事件,但是总感觉还缺少点什么东西,例如分隔线。很遗憾的是,RecyclerView没有提供ListView控件这样设置分割线的方法,不过它给我们提供了ItemDecoration类。这个ItemDecoration可以使得每一个Item在视觉上面进行分隔开来。RecyclerView没有要求ItemDecoration必须要设置,同样作为开发者可以选择不设置或者设置多个Decoration。然后RecyclerView会进行相应的绘制。

  • 我们这边定义了一个TestDecoration类,该类继承自RecyclerView.Decoration。只需要实现一下的两个方法即可:

onDraw(Canvas c,RecyclerView parent,RecyclerView.State state)
getItemOffset(Rect outRect,int itemPosition,RecyclerView parent)

  • 具体实现代码如下:
public class TestDecoration extends RecyclerView.ItemDecoration {  
    //采用系统内置的风格的分割线  
    private static final int[] attrs=newint[]{android.R.attr.listDivider};  
    private Drawable mDivider;  
   
    public TestDecoration(Context context) {  
        TypedArray typedArray=context.obtainStyledAttributes(attrs);  
        mDivider=typedArray.getDrawable(0);  
        typedArray.recycle();  
    }  
   
    /** 
     * 进行自定义绘制 
     * @param c 
     * @param parent 
     * @param state 
     */  
    @Override  
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {  
        int top=parent.getPaddingTop();  
        int bottom=parent.getHeight()-parent.getPaddingBottom();  
        int childCount=parent.getChildCount();  
        for(int i=0;i<childCount;i++){  
            View child=parent.getChildAt(i);  
            RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
            intleft=child.getRight()+layoutParams.rightMargin;  
            intright=left+mDivider.getIntrinsicWidth();  
            mDivider.setBounds(left,top,right,bottom);  
            mDivider.draw(c);  
        }  
    }  
   
    @Override  
    public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {  
       outRect.set(0,0,mDivider.getIntrinsicWidth(),0);  
    }  
}  
  • 最后给RecyclerView添加该分隔线即可:
//设置分割线  
gallery_recycler.addItemDecoration(new TestDecoration(this));  
  • 运行效果大致如下:


五、升级之分割线改造

  • 仔细看上面的运行效果,我们会发现一个问题,那就是分割线垂直分布,但是没有自适应控件的高度,直接延伸到界面的底部了。重新检查了有关的所有布局文件发现,高度都设置成了warp_content,但是实际的效果还是没有自适应。原来在哪里呢?

  • 真正的原因是因为RecyclerView控件已经不负责每一项VIew的显示了,那我们来看LayoutManger(布局管理器)该进行负责Item的布局显示了,所以我们需要进行实现一个LayoutManger,然后重写里边的onMeasure()方法。计算高度即可,具体代码如下:

public class CustomLinearLayoutManager extends LinearLayoutManager {  
    public CustomLinearLayoutManager(Context context) {  
        super(context);  
    }  
   
    @Override  
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {  
        Viewview=recycler.getViewForPosition(0);  
        if(view!=null){  
            measureChild(view,widthSpec,heightSpec);  
            int mWidth=View.MeasureSpec.getSize(widthSpec);  
            intmHeight=view.getMeasuredHeight();  
            setMeasuredDimension(mWidth,mHeight);  
        }  
    }  
}  
  • 然后RecyclerView使用CustomLinearLayoutManger即可,运行效果如下:


六、升级之添加删除Item动画

  • RecyclerView控件的一个优美之处就是当里边Item发生变化的时候可以加入相应的动画效果,涉及的类为RecyclerView.ItemAnimatior。一般当存在以下三种操作的时候可以加入动画效果:

Item 删除
Item 添加
Item 移动

  • 当我们的数据变化,或者移动的时候,用Adapter给我们提供的以下两个方法即可:

notifyItemInserted(int position)
notifyItemRemoved(int position)

  • 那我们可以在Adapter中加入两个方法,分别为添加Item和删除Item的方法:
//添加数据  
  public void addItem(GalleryModel model, int position) {  
      models.add(position, model);  
      notifyItemInserted(position);  
  }  
  //删除数据  
  public void removeItem(int position) {  
      models.remove(position);  
      notifyItemRemoved(position);  
  }  
  • 然后在外部进行调用这两个方法:
//添加数据
 adapter.addItem(new GalleryModel(R.drawable.ic_item_gallery,"Item Add"),3);  
//移除数据
dapter.removeItem(2);  
  • 最后千万不要忘记给RecyclerView设置动画效果,我这边就直接采用默认动画了。
//设置动画  
gallery_recycler.setItemAnimator(new DefaultItemAnimator());  
  • 最终运行效果如下:


七、最后总结

  • 今天通过实例带大家又重新把RecyclerView的相关使用讲解了一遍,实现类似Gallery效果,当然实例中还有很多缺点,需要进一步优化,后面的文章中也会继续更新的~

  • 本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:https://github.com/jiangqqlmj/FastDev4Android 同时欢迎大家star和fork整个开源快速开发框架项目~下一讲我们会进行RecyclerView集合AA(Android Annotations)注入框架来实现实例,敬请期待!

  • 再次声明:本文转载自【江清清的博客】http://blog.csdn.net/developer_jiangqq/article/details/49946589

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容