Android RecyclerView完全解析

RecyclerView完全解析

(一) 前言

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

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

那么今天我们首先来看第一部分:RecyclerView控件的基本使用,进阶,动画相关知识点。本次讲解所有用的Demo例子已经全部更新到下面的项目中了,欢迎大家star和fork。

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

(二) RecyclerView基本介绍

         通过使用RecyclerView控件,我们可以在APP中创建带有Material Design风格的复杂列表。RecyclerView控件和ListView的原理有很多相似的地方,都是维护少量的View来进行显示大量的数据,不过RecyclerView控件比ListView更加高级并且更加灵活。当我们的数据因为用户事件或者网络事件发生改变的时候也能很好的进行显示。和ListView不同的是,RecyclerView不用在负责Item的显示相关的功能,在这边所有有关布局,绘制,数据绑定等都被分拆成不同的类进行管理,下面我这边会一个个的进行讲解。同时RecyclerView控件提供了以下两种方法来进行简化和处理大数量集合:

  1. 采用LayoutManager来处理Item的布局
  2. 提供Item操作的默认动画,例如在增加或者删除item的时候

你也可以自定义LayoutManager或者设置添加/删除的动画,整体的RecyclerView结构图如下:

为了使用RecyclerView控件,我们需要创建一个Adapter和一个LayoutManager:

Adapter:继承自RecyclerView.Adapetr类,主要用来将数据和布局item进行绑定。

LayoutManager:布局管理器,设置每一项view在RecyclerView中的位置布局以及控件item view的显     

示或者隐藏.当View重用或者回收的时候,LayoutManger都会向Adapter来请求新的数据来进行替换原来数据的内容。这种回收重用的机制可以提供性能,避免创建很多的view或者是频繁的调用findViewById方法。这种机制和ListView还是很相似的。

      RecyclerView提供了三种内置的LayoutManager:

  1. LinearLayoutManager:线性布局,横向或者纵向滑动列表
  2. GridLayoutManager:表格布局
  3. StaggeredGridLayoutManager:流式布局,例如瀑布流效果

当然除了上面的三种内部布局之外,我们还可以继承RecyclerView.LayoutManager来实现一个自定义的LayoutManager。

      Animations(动画)效果:

      RecyclerView对于Item的添加和删除是默认开启动画的。我们当然也可以通过RecyclerView.ItemAnimator类定制动画,然后通过RecyclerView.setItemAnimator()方法来进行使用。

      RecyclerView相关类:

(三) RecyclerView基本实现

1.导入 android-support-v7-recyclerview.jar 包。

下载地址:http://down.51cto.com/data/2164646 

2.新建布局,引入RecyclerView控件:

 1 <?xmlversionxmlversion="1.0" encoding="utf-8"?>  
 2 <LinearLayoutxmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"  
 3      android:orientation="vertical"
 4      android:layout_width="match_parent"  
 5      android:layout_height="match_parent">  
 6      
 7      <include layout="@layout/common_top_bar_layout"/>  
 8     
 9      <android.support.v7.widget.RecyclerView  
10          android:id="@+id/recyclerView_one"  
11          android:layout_width="match_parent"  
12          android:layout_height="match_parent"  
13          android:scrollbars="vertical"  
14          />
15  
16 </LinearLayout>

3.在Activity中获取RecyclerView控件然后进行设置LayoutManger以及Adapter即可,和ListView的写法有点类似: 

 1 /** 
 2  * 当前类注释:RecyclerView使用实例测试demo 
 3  * 项目名:FastDev4Android 
 4  * 包名:com.chinaztt.fda.test 
 5  */  
 6 public class RecyclerViewTestActivity extends BaseActivity {  
 7     private LinearLayout top_bar_linear_back;  
 8     private TextView top_bar_title;  
 9     private RecyclerView recyclerView_one;  
10     private RecyclerView.Adapter mAdapter;  
11     private LinearLayoutManager mLayoutManager;  
12     @Override  
13     protected void onCreate(BundlesavedInstanceState) {  
14        super.onCreate(savedInstanceState);  
15        setContentView(R.layout.recyclerview_test_layout);  
16        top_bar_linear_back=(LinearLayout)this.findViewById(R.id.top_bar_linear_back);  
17        top_bar_linear_back.setOnClickListener(new CustomOnClickListener());  
18        top_bar_title=(TextView)this.findViewById(R.id.top_bar_title);  
19        top_bar_title.setText("RecyclerView使用实例");  
20         //开始设置RecyclerView  
21        recyclerView_one=(RecyclerView)this.findViewById(R.id.recyclerView_one);  
22         //设置固定大小  
23         recyclerView_one.setHasFixedSize(true);  
24         //创建线性布局  
25         mLayoutManager = newLinearLayoutManager(this);  
26          //垂直方向  
27        mLayoutManager.setOrientation(OrientationHelper.VERTICAL);  
28         //给RecyclerView设置布局管理器  
29        recyclerView_one.setLayoutManager(mLayoutManager);  
30         //创建适配器,并且设置  
31         mAdapter = newTestRecyclerAdapter(this);  
32         recyclerView_one.setAdapter(mAdapter);  
33     }  
34     class CustomOnClickListener implements View.OnClickListener{  
35         @Override  
36         public void onClick(View v) {  
37            RecyclerViewTestActivity.this.finish();  
38         }  
39     }  
40 }

4.自定义一个适配器来进行创建item view以及绑定数据:

 1 /** 
 2  * 当前类注释:RecyclerView 数据自定义Adapter 
 3  * 项目名:FastDev4Android 
 4  * 包名:com.chinaztt.fda.adapter.base  
 5  */  
 6 public class TestRecyclerAdapter extends RecyclerView.Adapter<TestRecyclerAdapter.ViewHolder>{  
 7     private LayoutInflater mInflater;  
 8     private String[] mTitles=null;  
 9     public TestRecyclerAdapter(Context context){  
10         this.mInflater=LayoutInflater.from(context);  
11         this.mTitles=new String[20];  
12         for (int i=0;i<20;i++){  
13             int index=i+1;  
14             mTitles[i]="item"+index;  
15         }  
16     }  
17     /** 
18      * item显示类型 
19      * @param parent 
20      * @param viewType 
21      * @return 
22      */  
23     @Override  
24     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
25         Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);  
26         //view.setBackgroundColor(Color.RED);  
27         ViewHolder viewHolder=new ViewHolder(view);  
28         return viewHolder;  
29     }  
30     /** 
31      * 数据的绑定显示 
32      * @param holder 
33      * @param position 
34      */  
35     @Override  
36     public void onBindViewHolder(ViewHolder holder, int position) {  
37        holder.item_tv.setText(mTitles[position]);  
38     }  
39    
40     @Override  
41     public int getItemCount() {  
42         return mTitles.length;  
43     }  
44    
45     //自定义的ViewHolder,持有每个Item的的所有界面元素  
46     public static class ViewHolder extends RecyclerView.ViewHolder {  
47         public TextView item_tv;  
48         public ViewHolder(View view){  
49             super(view);  
50             item_tv = (TextView)view.findViewById(R.id.item_tv);  
51         }  
52     }
53 }

这个自定义Adapter和我们在使用Listview时候的Adapter相比还是有点不太一样的,首先这边我们需要继承RecyclerView.Adaper类,然后实现两个重要的方法onBindViewHodler()以及onCreateViewHolder(),这边我们看出来区别,使用RecyclerView控件我们就可以把Item View视图创建和数据绑定这两步进行分来进行管理,用法就更加方便而且灵活了,并且我们可以定制打造千变万化的布局。同时这边我们还需要创建一个ViewHolder类,该类必须继承自RecyclerView.ViewHolder类,现在Google也要求我们必须要实现ViewHolder来承载Item的视图。

    该Demo运行效果如下:

 

上面的例子我们这边比较简单使用LinearLayoutManager来实现了,其中布局是采用垂直布局的,当然我们还可以设置线性布局的方向为横向,只要如下设置即可:

1 mLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);  

运行效果如下:

2.GridLayoutManger:使用如下设置:

1 StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2,OrientationHelper.VERTICAL);  
2 recyclerView_one.setLayoutManager(staggeredGridLayoutManager);  

(四) RecyclerView分隔线实现(ItemDecoration)

大家肯定观察到上面的显示效果还是比较丑,例如就没有分隔线这个效果,下面我们一起来实现以下分隔线的效果。还记得前面的一个表格中有写关于RecyclerView的相关类:

    RecyclerView.ItemDecoration   :   给每一项Item视图添加子View,可以进行画分隔线之类的东西

我们可以创建一个继承RecyclerView.ItemDecoration类来绘制分隔线,通过ItemDecoration可 以让我们每一个Item从视觉上面相互分开来,例如ListView的divider非常相似的效果。当然像我们上面的例子ItemDecoration 我们没有设置也没有报错哦,那说明ItemDecoration我们并不是强制需要使用,作为我们开发者可以设置或者不设置Decoration的。实现一个ItemDecoration,系统提供的ItemDecoration是一个抽象类,内部除去已经废弃的方法以外,我们主要实现以下三个方法:

 1 public static abstract class ItemDecoration {  
 2     public void onDraw(Canvas c,RecyclerView parent, State state) {  
 3       onDraw(c, parent);  
 4   }  
 5   public void onDrawOver(Canvas c,RecyclerView parent, State state) {  
 6       onDrawOver(c, parent);  
 7   }  
 8   public void getItemOffsets(RectoutRect, View view, RecyclerView parent, State state) {  
 9       getItemOffsets(outRect,((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);  
10   }  
11 }

又因为当我们RecyclerView在进行绘制的时候会进行绘制Decoration,那么会去调用onDraw和onDrawOver方法,那么这边我们其实只要去重写onDraw和getItemOffsets这两个方法就可以实现啦。然后LayoutManager会进行Item布局的时候,回去调用getItemOffset方法来计算每个Item的Decoration合适的尺寸,下面我们来具体实现一个Decoration。TestDecoration.java

 1 /** 
 2  * 当前类注释:自定义实现一个Decoration分隔线 
 3  * 项目名:FastDev4Android 
 4  * 包名:com.chinaztt.fda.widget 
 5  */  
 6 public class TestDecoration extends RecyclerView.ItemDecoration {  
 7     //采用系统内置的风格的分割线  
 8     private static final int[] attrs=newint[]{android.R.attr.listDivider};  
 9     private Drawable mDivider;  
10    
11     public TestDecoration(Context context) {  
12         TypedArray typedArray=context.obtainStyledAttributes(attrs);  
13         mDivider=typedArray.getDrawable(0);  
14     }  
15    
16     /** 
17      * 进行自定义绘制 
18      * @param c 
19      * @param parent 
20      * @param state 
21      */  
22     @Override  
23     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {  
24         int top=parent.getPaddingTop();  
25         intbottom=parent.getHeight()-parent.getPaddingBottom();  
26         int childCount=parent.getChildCount();  
27         for(int i=0;i<childCount;i++){  
28             View child=parent.getChildAt(i);  
29             RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
30             intleft=child.getRight()+layoutParams.rightMargin;  
31             intright=left+mDivider.getIntrinsicWidth();  
32            mDivider.setBounds(left,top,right,bottom);  
33             mDivider.draw(c);  
34         }  
35     }  
36    
37     @Override  
38     public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {  
39        outRect.set(0,0,mDivider.getIntrinsicWidth(),0);  
40     }  
41 }

我这边实例中采用系统主题(android.R.attr.listDivider)来设置成分隔线的,然后来获取尺寸,位置进行setBound(),绘制,接着通过outRect.set()来设置绘制整个区域范围,最后不要忘记往RecyclerView中设置该自定义的分割线,

1 //添加分隔线
2 recyclerView_one.addItemDecoration(newTestDecoration(this));  

运行效果如下:

上面的分割线效果只是垂直画了分割线,但是我们水平方向也要进行画分割线,那么我们下面对于自定义的Decoration进行改进,AdvanceDecoration.java就出现啦。

 1 /** 
 2  * 当前类注释:改进之后的自定义Decoration分割线 
 3  * 项目名:FastDev4Android 
 4  * 包名:com.chinaztt.fda.widget 
 5  */  
 6 public class AdvanceDecoration extends RecyclerView.ItemDecoration{  
 7     //采用系统内置的风格的分割线  
 8     private static final int[] attrs=newint[]{android.R.attr.listDivider};  
 9     private Drawable mDivider;  
10     private int orientation;  
11     public AdvanceDecoration(Contextcontext,int orientation) {  
12         TypedArray typedArray=context.obtainStyledAttributes(attrs);  
13         mDivider=typedArray.getDrawable(0);  
14         typedArray.recycle();  
15         this.orientation=orientation;  
16     }  
17     @Override  
18     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {  
19                  drawHDeraction(c,parent);  
20                  drawVDeraction(c,parent);  
21     }  
22     /** 
23      * 绘制水平方向的分割线 
24      * @param c 
25      * @param parent 
26      */  
27     private void drawHDeraction(Canvas c,RecyclerView parent){  
28         int left=parent.getPaddingLeft();  
29         intright=parent.getWidth()-parent.getPaddingRight();  
30         int childCount=parent.getChildCount();  
31         for(int i=0;i<childCount;i++){  
32             View child=parent.getChildAt(i);  
33             RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
34             inttop=child.getBottom()+layoutParams.bottomMargin;  
35             intbottom=top+mDivider.getIntrinsicHeight();  
36            mDivider.setBounds(left,top,right,bottom);  
37             mDivider.draw(c);  
38         }  
39     }  
40     /** 
41      * 绘制垂直方向的分割线 
42      * @param c 
43      * @param parent 
44      */  
45     private void drawVDeraction(Canvas c,RecyclerView parent){  
46         int top=parent.getPaddingTop();  
47         intbottom=parent.getHeight()-parent.getPaddingBottom();  
48         int childCount=parent.getChildCount();  
49         for(int i=0;i<childCount;i++){  
50             View child=parent.getChildAt(i);  
51             RecyclerView.LayoutParamsla youtParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
52             intleft=child.getRight()+layoutParams.rightMargin;  
53             intright=left+mDivider.getIntrinsicWidth();  
54            mDivider.setBounds(left,top,right,bottom);  
55             mDivider.draw(c);  
56         }  
57     }  
58     @Override  
59     public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {  
60        if(OrientationHelper.HORIZONTAL==orientation){  
61             outRect.set(0, 0,mDivider.getIntrinsicWidth(), 0);  
62         }else {  
63             outRect.set(0, 0, 0,mDivider.getIntrinsicHeight());  
64         }  
65     }  
66 }

改良之后的自定义分割器的构造函数中新增一个int参数,用来表示横向还是纵向布局,这样我们可以分别来绘制分割线。具体使用方法:

1 recyclerView_one.addItemDecoration(newAdvanceDecoration(this,OrientationHelper.VERTICAL));

运行比较效果如下:

(1) 横向

(2) 纵向

(五) RecyclerView高级用户(监听事件处理)

我们知道在ListView使用的时候,该控件给我们提供一个onItemClickListener监听器,这样当我们的item发生触发事件的时候,会回调相关的方法,以便我们方便处理Item点击事件。对于RecyclerView来讲,非常可惜的时候,该控件没有给我们提供这样的内置监听器方法,不过我们可以进行改造实现。我们先来看一下之前我们写得TestRecyclerAdapter中的onCreateViewHolder()方法中的代码:

1 public ViewHolder onCreateViewHolder(ViewGroupparent, int viewType) {  
2        Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);  
3        //这边可以做一些属性设置,甚至事件监听绑定  
4        //view.setBackgroundColor(Color.RED);  
5        ViewHolder viewHolder=newViewHolder(view);  
6        return viewHolder;  
7 } 

该方法创建一个ViewHolder,其中承载的就是每一项Item View视图,那么我们可以在view创建出来之后给它进行添加相应的属性或者监听方法,例如:背景颜色,大小,以及点击事件。既然可以这样解决,OK,我们给View添加一个onClickListener监听器,然后点击的时候回调onClick()方法。同时我们需要自定义一个类似于onItemClickListener()的监听器来处理。

 1 /** 
 2  * 自定义RecyclerView 中item view点击回调方法 
 3  */  
 4 interface OnRecyclerItemClickListener{  
 5     /** 
 6      * item view 回调方法 
 7      * @param view  被点击的view 
 8      * @param position 点击索引 
 9      */  
10     void onItemClick(View view, intposition);  
11 }

然后声明以及Adapter初始化的时候传入进去:

1 public TestRecyclerAdapter(Contextcontext,OnRecyclerItemClickListener onRecyclerItemClickListener){  
2     ....... 
3    this.onRecyclerItemClickListener=onRecyclerItemClickListener;  
4 }

然后我们在onClick回调方法中调用OnRecyclerItemClickListener接口的方法。

1 view.setOnClickListener(new View.OnClickListener() {  
2     @Override  
3     public void onClick(View v) {  
4        if(onRecyclerItemClickListener!=null){  
5            onRecyclerItemClickListener.onItemClick(view, (int)view.getTag());  
6         }  
7     }  
8 });

上面的onItemClick中第二个参数的position,采用view.getTag的方法获取,那么我们就需要在onBindViewHolder()方法中设置一个tag了。

1 public voidonBindViewHolder(ViewHolder holder, int position) {  
2     holder.item_tv.setText(mTitles[position]);  
3     holder.itemView.setTag(position);  
4 }

最后我们在外部使用一下接口:

1 mAdapter = new TestRecyclerAdapter(this, new TestRecyclerAdapter.OnRecyclerItemClickListener() {  
2     @Override  
3     public void onItemClick(View view,int position) {  
4        Toast.makeText(RecyclerViewTestActivity.this, "点击了第"+position+"项", Toast.LENGTH_SHORT).show();  
5     }  
6 });

运行结果如下:

(六) RecyclerView数据添加删除处理

     讲了以上RecyclerView中各种用户,处理之后,现在我们来看一下当数据发生变化之后的处理。例如我们在使用ListView的时候,当数据发生变化的时候可以通过notifyDatasetChange()来刷新界面。对于RecyclerView控件来讲,给我们提供更加高级的使用方法notifyItemInserted(position)和notifyItemRemoved(position)

     我们可以在TestRecyclerAdapter中添加数据新增和数据删除的方法如下:

 1 //添加数据  
 2 public void addItem(String data, intposition) {  
 3    mTitles.add(position, data);  
 4    notifyItemInserted(position);  
 5 }  
 6 //删除数据  
 7 public void removeItem(String data) {  
 8    int position = mTitles.indexOf(data);  
 9    mTitles.remove(position);  
10    notifyItemRemoved(position);  
11 }  

然后我们在Activity中进行调用即可:

1 //添加数据  
2 mAdapter.addItem("additem",5);  
3 //删除数据  
4 mAdapter.removeItem("item4");  

在运行之前我们不要忘记RecyclerView给提供了动画设置,我这边就直接采用了默认动画,设置方法如下:

1 //添加默认的动画效果  
2 recyclerView_one.setItemAnimator(new DefaultItemAnimator());

最终运行效果如下图:

 

 

(七) RecyclerView总结

 到此为止就完成我们RecyclerView控件使用的第一讲内容,其中包括控件的基本介绍,基本使用,高级用法(自定义间隔符,加入点击监听事件以及Item添加删除动画处理)。 总体来讲RecyclerView控件是非常不错,尤其在布局以及数据绑定,动画方面,除了系统内置的三种布局方式之外,我们还可以定制出我们自己的布局 管理器。同时当item数据发生变化的时候还给我们提供非常炫的效果。相信大家在今天这一讲之后,会越来越爱上RecyclerView控件的使用,从此 可以抛弃ListView和GridView啦.

本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:

https://github.com/jiangqqlmj/FastDev4Android 同时欢迎大家star和fork整个开源快速开发框架项目~下一讲我们会通过一个具体实例来自定义实现一个广告条控件实例。   

原文地址:https://www.cnblogs.com/steffen/p/5168118.html