Android 高级UI设计笔记07:RecyclerView 的详解

1. RecyclerView介绍

      Android应用程序中列表是一个非常重要的控件,适用场合非常多,如新闻列表、应用列表、消息列表等等,但是从Android 一出生到现在并没有非常好用的列表控件,早期的 ListView 用法非常复杂,尤其是自定义列表,简直就是地狱,因为其中还涉及到很多效率优化的问题,新手很难写出高效率的基于列表应用,而且 ListView 只能垂直方向呈现内容,使用很不灵活,为了解决这个缺陷,Android 官方推出了 RecyclerView 控件,用来替代 ListView。

(1)RecyclerView的控件相对于ListView他好在哪里呢

  1)它封装了viewholder的回收复用

      2)RecyclerView使用布局管理器管理子view的位置,也就是说你再不用拘泥于ListView的竖直线性展示方式。通过设置LayoutManager来快速实现listviewgridview瀑布流的效果;而且还可以设置横向纵向显示

  3)带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果。

  4)分开的view

我们平时用listview的时候,adapter一般这么写的:

 1 if (convertView == null) {
 2       holder = new ViewHolder();
 3        LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
 4        convertView = inflater.inflate(
 5                R.layout.list_device_binding, parent, false);
 6 
 7        holder.deviceImage = (ImageView) convertView
 8                .findViewById(R.id.bluetoothDeviceImage);
 9        holder.deviceName = (TextView) convertView
10                .findViewById(R.id.bluetoothDeviceName);
11        holder.deviceType = (TextView) convertView
12                .findViewById(R.id.bluetoothDeviceType);
13        convertView.setTag(holder);
14    } else {
15        holder = (ViewHolder) convertView.getTag();
16    }

但是,到了这里,RecyclerView分隔开了,如下:

 1 @Override
 2 public A onCreateViewHolder(ViewGroup parent, int viewType) {       
 3     final View view = LayoutInflater.from(mContext).
 4           inflate(R.layout.listitem_track_history, parent, false);
 5     return new ViewHolder(view); 
 6  }  
 7 
 8 @Override
 9 public void onBindViewHolder(A holder, int position) {
10      Data da=getData(position);
11     holder.tvDate.setText(da.getDate());
12 }

  5)相对简单的层次结构

我们看下Listview他背后的继承关系:

1 public class ListView extends AbsListView 
2 public abstract class AbsListView extends AdapterView<ListAdapter>
3 public abstract class AdapterView<T extends Adapter> extends ViewGroup 

三重继承,内容还挺多的,不是直接继承ViewGroup,而相反的RecycleView却是直接继承自ViewGroup的

(2)RecyclerView的控件相对于ListView,他有什么缺陷呢?(目前只知道1个缺点)

  1)不能简单的添加点击事件onListItemClickListener 

我们在使用listview设置子item的点击事件的时候,只需要像下面这么写

1 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
2        @Override
3         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
4 
5            User user= parent.getItemAtPosition(position);                   
6         }
7 });

但是,如果你使用这个RecycleView,会发现没有这个接口了。RecyclerView不再负责Item视图的布局及显示,所以RecyclerView也没有为Item开放OnItemClick等点击事件。

这就需要我们自己实现,见我的博客:Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件

(3)RecyclerView 核心内容
  • Android RecyclerView 的用法
  • Android RecyclerView 横向布局
  • Android RecyclerView 垂直布局
  • Android RecyclerView 表格布局

这里我们使用的Android Studio,首先新建Android工程之后,右击app,打开  "Open Module Setting" ,然后找到"Dependencies"选项,添加RecyclerView依赖库,如下:

在gradle中添加依赖

dependencies {

    compile fileTree(dir: 'libs', include: ['*.jar'])

    testCompile 'junit:junit:4.12'

    compile 'com.android.support:appcompat-v7:23.4.0'

    compile 'com.android.support:recyclerview-v7:23.4.0'

}

当然也有很多朋友使用Eclipse开发工具(建议大家尽快使用AS,AS比Eclipse高效太多了),如果要使用RecyclerView也很简单,只要添加一个jar包到工程的libs文件下,然后构建路径就可以使用了,当然静态布局设计的时候还是注意要引用RecyclerView的全路径。

这个jar包为android-support-v7-recyclerview.jar  和 android-support-v4.jar,这个两个jar包往往会因为版本不一致产生冲突,从而会报错:

java.lang.RuntimeException: Unable to start activity ComponentInfo

这里是安全使用的v4 和 v7 库的下载地址为:http://download.csdn.net/detail/hebao5201314/9567555

那么这里我先讲解使用RecyclerView主要方法,如下:

   1)setLayoutManager(layout):控制显示方式

RecyclerView提供了三种LayoutManager:LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager

   2)setItemAnimator():控制item的增删动画

   3)addItemDecoration(): 控制item间的间隔,这里还可以自定义间隔的样式。

——Adapter:使用RecyclerView时,我们需要一个集成RecyclerView.Adapter的适配器,作用是将数据与每个item的界面进行绑定。

——LayoutManager:用来确定每个item的布局,何时展示和隐藏。回收或重用的时候LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制和ListView的原理类似,都避免了创建过多的View和频繁的调用findViewById方法。

目前SDK中提供了三种自带的LayoutManager:

 1)LinearLayoutManager

 2)GridLayoutManager

 3)StaggeredGridLayoutManager

2. 接下来我们就可以使用RecyclerView。

(1)首先我们来到MainActivity里面,如下:

 1 package com.example.hebao.learnrv;
 2 
 3 import android.support.v7.app.AppCompatActivity;
 4 import android.os.Bundle;
 5 import android.support.v7.widget.LinearLayoutManager;
 6 import android.support.v7.widget.RecyclerView;
 7 import android.view.Menu;
 8 import android.view.MenuItem;
 9 import android.view.View;
10 import android.view.ViewGroup;
11 import android.widget.TextView;
12 
13 public class MainActivity extends AppCompatActivity {
14 
15     private RecyclerView rv;
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         rv = new RecyclerView(this);
20          //先加载RecyclerView
21         setContentView(rv);
22          //然后动态设置RecyclerView
23         rv.setLayoutManager(new LinearLayoutManager(this));
24         rv.setAdapter(new RecyclerView.Adapter(){
25             class ViewHolder extends  RecyclerView.ViewHolder {
26                 private TextView tv;
27 
28                 public ViewHolder(TextView itemView) {
29                     super(itemView);
30                     tv = itemView;
31                 }
32 
33                 public TextView getTv() {
34                     return tv;
35                 }
36             }
37 
38             /**
39              * 创建ViewHolder
40              * @param viewGroup
41              * @param i
42              * @return
43              */
44             @Override
45             public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
46                 return new ViewHolder(new TextView(viewGroup.getContext()));
47             }
48 
49             /**
50              * 对上面自己创建的ViewHolder携带数据进行处理
51              * @param viewHolder
52              * @param i
53              */
54             @Override
55             public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
56                 ViewHolder vh  = (ViewHolder) viewHolder;
57                 vh.getTv().setText("Item "+i);
58             }
59 
60             /**
61              *
62              * 获取RecyclerView的子对象数量
63              */
64             @Override
65             public int getItemCount() {
66                 return 100;
67             }
68         } );
69     }
70 
71 
72 }

布署程序到模拟器上,如下:(可以上下滑动滚动

(2)利用数组承载数据源:

 1 package com.example.hebao.learnrv;
 2 
 3 import android.support.v7.app.AppCompatActivity;
 4 import android.os.Bundle;
 5 import android.support.v7.widget.LinearLayoutManager;
 6 import android.support.v7.widget.RecyclerView;
 7 import android.view.Menu;
 8 import android.view.MenuItem;
 9 import android.view.View;
10 import android.view.ViewGroup;
11 import android.widget.TextView;
12 
13 public class MainActivity extends AppCompatActivity {
14 
15     private RecyclerView rv;
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         rv = new RecyclerView(this);
20 
21         setContentView(rv);
22 
23         rv.setLayoutManager(new LinearLayoutManager(this));
24         rv.setAdapter(new RecyclerView.Adapter(){
25             private String[] data = new String[] {"hello", "二胎时代","九阴真经"};
26             class ViewHolder extends  RecyclerView.ViewHolder {
27                 private TextView tv;
28 
29                 public ViewHolder(TextView itemView) {
30                     super(itemView);
31                     tv = itemView;
32                 }
33 
34                 public TextView getTv() {
35                     return tv;
36                 }
37             }
38 
39             /**
40              * 创建ViewHolder
41              * @param viewGroup
42              * @param i
43              * @return
44              */
45             @Override
46             public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
47                 return new ViewHolder(new TextView(viewGroup.getContext()));
48             }
49 
50             /**
51              * 对上面自己创建的ViewHolder携带数据进行处理
52              * @param viewHolder
53              * @param i
54              */
55             @Override
56             public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
57                 ViewHolder vh  = (ViewHolder) viewHolder;
58                 vh.getTv().setText(data[i]);
59             }
60 
61             /**
62              *
63              * 获取RecyclerView的子对象数量
64              */
65             @Override
66             public int getItemCount() {
67                 return data.length;
68             }
69         });
70     }
71 
72 
73 }

布署程序到模拟器上,如下:

3. 使用资源文件自定义列表项:
(1)首先来到MainActivity,如下:
 1 package com.example.hebao.learnrv;
 2 
 3 import android.support.v7.app.AppCompatActivity;
 4 import android.os.Bundle;
 5 import android.support.v7.widget.LinearLayoutManager;
 6 import android.support.v7.widget.RecyclerView;
 7 
 8 public class MainActivity extends AppCompatActivity {
 9 
10     private RecyclerView rv;
11     @Override
12     protected void onCreate(Bundle savedInstanceState) {
13         super.onCreate(savedInstanceState);
14         rv = new RecyclerView(this);
15 
16         setContentView(rv);
17 
18         rv.setLayoutManager(new LinearLayoutManager(this));
19         rv.setAdapter(new MyAdapter());
20     }
21 
22 
23 }

(2)此时MyAdapter,如下:

 1 package com.example.hebao.learnrv;
 2 
 3 import android.support.v7.widget.RecyclerView;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.TextView;
 8 
 9 /**
10  * Created by hebao on 11/16/15.
11  */
12 class MyAdapter extends RecyclerView.Adapter {
13     private ItemData[] data = new ItemData[] {new ItemData("东邪", "黄药师"), new ItemData("西狂","杨过")};
14 
15     class ViewHolder extends RecyclerView.ViewHolder {
16         private View root;
17         private TextView tvTitle, tvContent;
18 
19         public ViewHolder(View root) {
20             super(root);
21             tvTitle = (TextView) root.findViewById(R.id.tvTitle);
22             tvContent = (TextView) root.findViewById(R.id.tvContent);
23         }
24 
25         public TextView getTvContent() {
26             return tvContent;
27         }
28 
29         public TextView getTvTitle() {
30             return tvTitle;
31         }
32     }
33 
34     /**
35      * 创建ViewHolder
36      *
37      * @param viewGroup
38      * @param i
39      * @return
40      */
41     @Override
42     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
43         return new ViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_cell,
44                 null));
45     }
46 
47     /**
48      * 对上面自己创建的ViewHolder携带数据进行处理
49      *
50      * @param viewHolder
51      * @param i
52      */
53     @Override
54     public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
55         ViewHolder vh = (ViewHolder) viewHolder;
56         ItemData id = data[i];
57         vh.getTvTitle().setText(id.title);
58         vh.getTvContent().setText(id.content);
59 
60     }
61 
62     /**
63      * 获取RecyclerView的子对象数量
64      */
65     @Override
66     public int getItemCount() {
67         return data.length;
68     }
69 
70 
71 
72 }

此处我们用到了一个实体数据类ItemData,如下:

 1 package com.example.hebao.learnrv;
 2 
 3 /**
 4  * Created by hebao on 11/16/15.
 5  */
 6 public class ItemData {
 7     public String title = "title";
 8     public String content ="content";
 9 
10     public ItemData(String title, String content) {
11         this.title = title;
12         this.content = content;
13     }
14 }

自定义布局文件资源为list_cell.xml:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:orientation="vertical" android:layout_width="match_parent"
 4     android:layout_height="match_parent">
 5 
 6     <TextView
 7         android:layout_width="fill_parent"
 8         android:layout_height="wrap_content"
 9         android:textAppearance="?android:attr/textAppearanceLarge"
10         android:text="Large Text"
11         android:id="@+id/tvTitle"
12         android:layout_gravity="center_horizontal" />
13 
14     <TextView
15         android:layout_width="fill_parent"
16         android:layout_height="wrap_content"
17         android:text="New Text"
18         android:id="@+id/tvContent"
19         android:layout_gravity="center_horizontal" />
20 </LinearLayout>

(3)程序布署到模拟器上,如下:

 
相信大家看到上面的显示效果Item之间没有分割线,该怎么办呢?

这里我们需要用到RecyclerView.ItemDecoration给每一项Item视图添加子View,可以进行画分隔线之类的东西

那么具体如何使用呢

    我们可以创建一个类继承RecyclerView.ItemDecoration类来绘制分隔线,通过ItemDecoration可 以让我们每一个Item从视觉上面相互分开来,实现一个ItemDecoration,系统提供的ItemDecoration是一个抽象类,因为当我们RecyclerView在进行绘制的时候会进行绘制。

下面是转载别人实现的RecycleViewDivider,如下:

  1 package com.himi.recyclerviewdemo;
  2 
  3 import android.content.Context;
  4 import android.content.res.TypedArray;
  5 import android.graphics.Canvas;
  6 import android.graphics.Paint;
  7 import android.graphics.Rect;
  8 import android.graphics.drawable.Drawable;
  9 import android.support.v4.content.ContextCompat;
 10 import android.support.v7.widget.LinearLayoutManager;
 11 import android.support.v7.widget.RecyclerView;
 12 import android.support.v7.widget.RecyclerView.ItemDecoration;
 13 import android.view.View;
 14 
 15 public class RecycleViewDivider extends ItemDecoration {
 16       private Paint mPaint;
 17         private Drawable mDivider;
 18         private int mDividerHeight = 2;//分割线高度,默认为1px
 19         private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
 20         private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
 21 
 22         /**
 23          * 默认分割线:高度为2px,颜色为灰色
 24          *
 25          * @param context
 26          * @param orientation 列表方向
 27          */
 28         public RecycleViewDivider(Context context, int orientation) {
 29             if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
 30                 throw new IllegalArgumentException("请输入正确的参数!");
 31             }
 32             mOrientation = orientation;
 33 
 34             final TypedArray a = context.obtainStyledAttributes(ATTRS);
 35             mDivider = a.getDrawable(0);
 36             a.recycle();
 37         }
 38 
 39         /**
 40          * 自定义分割线
 41          *
 42          * @param context
 43          * @param orientation 列表方向
 44          * @param drawableId  分割线图片
 45          */
 46         public RecycleViewDivider(Context context, int orientation, int drawableId) {
 47             this(context, orientation);
 48             mDivider = ContextCompat.getDrawable(context, drawableId);
 49             mDividerHeight = mDivider.getIntrinsicHeight();
 50         }
 51 
 52         /**
 53          * 自定义分割线
 54          *
 55          * @param context
 56          * @param orientation   列表方向
 57          * @param dividerHeight 分割线高度
 58          * @param dividerColor  分割线颜色
 59          */
 60         public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
 61             this(context, orientation);
 62             mDividerHeight = dividerHeight;
 63             mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 64             mPaint.setColor(dividerColor);
 65             mPaint.setStyle(Paint.Style.FILL);
 66         }
 67 
 68 
 69         //获取分割线尺寸
 70         @Override
 71         public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
 72             super.getItemOffsets(outRect, view, parent, state);
 73             outRect.set(0, 0, 0, mDividerHeight);
 74         }
 75 
 76         //绘制分割线
 77         @Override
 78         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 79             super.onDraw(c, parent, state);
 80             if (mOrientation == LinearLayoutManager.VERTICAL) {
 81                 drawVertical(c, parent);
 82             } else {
 83                 drawHorizontal(c, parent);
 84             }
 85         }
 86 
 87         //绘制横向 item 分割线
 88         private void drawHorizontal(Canvas canvas, RecyclerView parent) {
 89             final int left = parent.getPaddingLeft();
 90             final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
 91             final int childSize = parent.getChildCount();
 92             for (int i = 0; i < childSize; i++) {
 93                 final View child = parent.getChildAt(i);
 94                 RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
 95                 final int top = child.getBottom() + layoutParams.bottomMargin;
 96                 final int bottom = top + mDividerHeight;
 97                 if (mDivider != null) {
 98                     mDivider.setBounds(left, top, right, bottom);
 99                     mDivider.draw(canvas);
100                 }
101                 if (mPaint != null) {
102                     canvas.drawRect(left, top, right, bottom, mPaint);
103                 }
104             }
105         }
106 
107         //绘制纵向 item 分割线
108         private void drawVertical(Canvas canvas, RecyclerView parent) {
109             final int top = parent.getPaddingTop();
110             final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
111             final int childSize = parent.getChildCount();
112             for (int i = 0; i < childSize; i++) {
113                 final View child = parent.getChildAt(i);
114                 RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
115                 final int left = child.getRight() + layoutParams.rightMargin;
116                 final int right = left + mDividerHeight;
117                 if (mDivider != null) {
118                     mDivider.setBounds(left, top, right, bottom);
119                     mDivider.draw(canvas);
120                 }
121                 if (mPaint != null) {
122                     canvas.drawRect(left, top, right, bottom, mPaint);
123                 }
124             }
125         }
126     }

使用方法:

添加默认分割线:高度为2px,颜色为灰色

  mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.VERTICAL));

添加自定义分割线:可自定义分割线drawable

  mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage));

添加自定义分割线:可自定义分割线高度和颜色

  mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.VERTICAL, 10, getResources().getColor(R.color.divide_gray_color)));

使用很简单,这里我就不添加代码实现了。

4. RecyclerView的布局样式
(1)RecyclerView的横向布局:
修改2上面代码,如下:
list_cell.xml
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:orientation="vertical" android:layout_width="match_parent"
 4     android:layout_height="match_parent">
 5 
 6     <TextView
 7         android:layout_width="wrap_content"
 8         android:layout_height="wrap_content"
 9         android:textAppearance="?android:attr/textAppearanceLarge"
10         android:text="Large Text"
11         android:id="@+id/tvTitle"
12         android:layout_gravity="center_horizontal" />
13 
14     <TextView
15         android:layout_width="wrap_content"
16         android:layout_height="wrap_content"
17         android:text="New Text"
18         android:id="@+id/tvContent"
19         android:layout_gravity="center_horizontal" />
20 </LinearLayout>

同时来到MainActivity,如下:

 1 package com.example.hebao.learnrv;
 2 
 3 import android.support.v7.app.AppCompatActivity;
 4 import android.os.Bundle;
 5 import android.support.v7.widget.LinearLayoutManager;
 6 import android.support.v7.widget.RecyclerView;
 7 
 8 public class MainActivity extends AppCompatActivity {
 9 
10     private RecyclerView rv;
11     @Override
12     protected void onCreate(Bundle savedInstanceState) {
13         super.onCreate(savedInstanceState);
14         rv = new RecyclerView(this);
15 
16         setContentView(rv);
17         //设置RecyclerView为横向布局
18         rv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));
19         rv.setAdapter(new MyAdapter());
20     }
21 
22 
23 }

布署程序到模拟器上,如下:

 
 
(2)RecyclerView的垂直布局
rv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
 
(3)RecyclerView的表格布局:
rv.setLayoutManager(new GridLayoutManager(this,3));
原文地址:https://www.cnblogs.com/hebao0514/p/4968072.html