zoukankan      html  css  js  c++  java
  • 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));
  • 相关阅读:
    NGINX
    nginx修改上传文件大小限制
    Mysql主从复制机制原理
    MongoDB系列---用户及权限管理02
    MongoDB系列---入门安装操作01
    浅谈原理--hashCode方法
    ActiveMQ学习总结------原生实战操作(下)03
    dubbo配置负载均衡、集群环境
    ActiveMQ学习总结------入门篇01
    vsftpd上传文件大小为0(主动模式)
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4968072.html
Copyright © 2011-2022 走看看