Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件

Posted 鸿钧老祖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件相关的知识,希望对你有一定的参考价值。

1. 引言:

RecyclerView侧重的是布局的灵活性,虽说可以替代ListView但是连基本的点击事件都没有,这篇文章就来详细讲解如何为RecyclerView的item添加点击事件,顺便复习一下观察者模式。

 

2. 最终目的

模拟ListView的setOnItemClickListener()方法,调用者只须调用类似于setOnItemClickListener的东西就能获得被点击item的相关数据。

 

3. 原理

为RecyclerView的每个子item设置setOnClickListener,然后在onClick中再调用一次对外封装的接口,将这个事件传递给外面的调用者。而"为RecyclerView的每个子item设置setOnClickListener"在Adapter中设置。其实直接在onClick中也能完全处理item的点击事件,但是这样会破坏代码的逻辑。

 

4. 具体步骤如下:

在自定义MyAdapter之中(继承自RecyclerView.Adapter

(1)在MyAdapter中定义如下接口,模拟ListView的OnItemClickListener:

 //define interface
    public static interface OnRecyclerViewItemClickListener {
        void onItemClick(View view , String data);
    }

 

(2)声明一个这个接口的变量

private OnRecyclerViewItemClickListener mOnItemClickListener = null;

 

(3)在onCreateViewHolder()中为每个item添加点击事件

 @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup,  int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
        ViewHolder vh = new ViewHolder(view);
        //将创建的View注册点击事件
        view.setOnClickListener(this);
        return vh;
    }

 

(4)将点击事件转移给外面的调用者:

 @Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            //注意这里使用getTag方法获取数据
            mOnItemClickListener.onItemClick(v,(String)v.getTag());
        }
    }

 

(5)注意上面调用接口的onItemClick()中的v.getTag()方法,这需要在onBindViewHolder()方法中设置和item相关的数据

@Override
    public void onBindViewHolder(ViewHolder viewHolder,  int position) {
        viewHolder.mTextView.setText(datas[position]);
        //将数据保存在itemView的Tag中,以便点击时进行获取
        viewHolder.itemView.setTag(datas[position]);
    }

(6)最后暴露给外面的调用者,定义一个设置Listener的方法():

 public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }

 以上所有步骤都发生在自定义的adapter中,典型的观察者模式,有点绕的地方在于,这里涉及到两个观察者模式的使用,view的setOnClickListener本来就是观察者模式,我们将这个观察者模式的事件监听传递给了我们自己的观察者模式。

 

(7)接下来当然是在Activity中使用,如下:

 1 mRecyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
 2         //创建默认的线性LayoutManager
 3         mLayoutManager = new LinearLayoutManager(this);
 4         mRecyclerView.setLayoutManager(mLayoutManager);
 5         //如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
 6         mRecyclerView.setHasFixedSize(true);
 7         //创建并设置Adapter
 8         mAdapter = new MyAdapter(data);
 9         mRecyclerView.setAdapter(mAdapter);
10         mAdapter.setOnItemClickListener(new OnRecyclerViewItemClickListener(){
11             @Override    
12             public void onItemClick(View view , String data){
13                 Toast.makeText(MainActivity.this, data, 600).show();
14             }
15         });

 

5. 案例演示:(结合上面的步骤理解)

(1)使用Eclipse创建一个工程,如下:

同时注意API要使用API21

 

(2)首先我们来到主布局activity_main.xml,如下:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent" >
 5 
 6     <android.support.v7.widget.RecyclerView
 7         android:id="@+id/recyclerview"
 8         android:layout_width="match_parent"
 9         android:layout_height="match_parent"
10          />
11 
12 </RelativeLayout>

(3)接下来,我们来到RecyclerView的item布局item.xml,如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:layout_width="match_parent"
 5     android:layout_height="50dip" >
 6 
 7     <TextView
 8         android:id="@+id/text"
 9         android:text="默认"
10         android:layout_marginTop="5dp"
11         android:gravity="center_horizontal"
12         android:layout_width="match_parent"
13         android:layout_height="wrap_content"
14         android:textSize="20sp" 
15         android:textColor="@android:color/holo_red_dark"/>
16 
17 </RelativeLayout>

 

(4)接下来我们自定义MyAdapter,如下:

 1 package com.himi.recyclerviewdemo;
 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 public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> 
10                                 implements View.OnClickListener {
11 
12     private String[] datas;
13 
14     public MyAdapter(String[] datas) {
15         this.datas = datas;
16 
17     }
18 
19 
20     /**
21      * 1.定义接口
22      */
23     public static interface OnRecyclerViewItemClickListener {
24         
25         void onItemClick(View view, String data);
26         
27     }
28 
29     /**
30      * 3.在onCreateViewHolder()中为每个item添加点击事件
31      */
32     @Override
33     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
34         View view = LayoutInflater.from(viewGroup.getContext())
35                 .inflate(R.layout.item, viewGroup, false);
36         
37         ViewHolder vh = new ViewHolder(view);
38         // 将创建的View注册点击事件
39         view.setOnClickListener(this);
40         
41         return vh;
42     }
43     
44     /**
45      * 5.注意上面调用接口的onItemClick()中的v.getTag()方法,
46      * 这需要在onBindViewHolder()方法中设置和item相关的数据
47      */
48     @Override
49     public void onBindViewHolder(ViewHolder viewHolder, int position) {
50         
51         viewHolder.mTextView.setText(datas[position]);
52         
53         // 将数据保存在itemView的Tag中,以便点击时进行获取
54         viewHolder.itemView.setTag(datas[position]);
55     }
56 
57     /**
58      * 4.将点击事件转移给外面的调用者
59      */
60     public void onClick(View v) {
61         if (mOnItemClickListener != null) {
62             // 注意这里使用getTag方法获取数据
63             mOnItemClickListener.onItemClick(v, (String) v.getTag());
64         }
65     }
66 
67     /**
68      * 2.声明一个这个接口的变量
69      */
70     private OnRecyclerViewItemClickListener mOnItemClickListener = null;
71     
72     
73     /**
74      *6.最后暴露给外面的调用者,定义一个设置Listener的方法()
75      */
76     
77     public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
78         this.mOnItemClickListener = listener;
79     }
80     
81     
82 
83     // 获取数据的数量
84     @Override
85     public int getItemCount() {
86         return datas.length;
87     }
88 
89     // 自定义的ViewHolder,持有每个Item的的所有界面元素
90     public static class ViewHolder extends RecyclerView.ViewHolder {
91         public TextView mTextView;
92 
93         public ViewHolder(View view) {
94             super(view);
95             mTextView = (TextView) view.findViewById(R.id.text);
96         }
97     }
98 
99 }

 

(5)接下来来到MainActivity,如下:

 1 package com.himi.recyclerviewdemo;
 2 
 3 import com.himi.recyclerviewdemo.MyAdapter.OnRecyclerViewItemClickListener;
 4 
 5 import android.app.Activity;
 6 import android.os.Bundle;
 7 import android.support.v7.widget.LinearLayoutManager;
 8 import android.support.v7.widget.RecyclerView;
 9 import android.view.View;
10 import android.widget.Toast;
11 
12 public class MainActivity extends Activity {
13 
14     private RecyclerView mRecyclerView;
15     private LinearLayoutManager mLayoutManager;
16     private MyAdapter mAdapter;
17     private String[] data = new String[] { 
18                 "刘德华", "周杰伦", "梁朝伟", "郭富城", "黎明", "张学友", 
19                 "成龙", "午马", "洪金宝", "林正英", "元彪", "林志颖", 
20                 "吴奇隆", "苏有朋", "赵薇", "陈坤", "周润发", "范冰冰", 
21                 "贾静雯", "周星驰" 
22             };
23 
24     @Override
25     protected void onCreate(Bundle savedInstanceState) {
26         super.onCreate(savedInstanceState);
27         setContentView(R.layout.activity_main);
28         mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
29 
30         // 创建默认的线性LayoutManager
31         mLayoutManager = new LinearLayoutManager(this);
32         mRecyclerView.setLayoutManager(mLayoutManager);
33 
34         // 如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
35         mRecyclerView.setHasFixedSize(true);
36         
37         //添加item间的分割线
38         mRecyclerView.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.HORIZONTAL));
39 
40         // 创建并设置Adapter
41         mAdapter = new MyAdapter(data);
42         mRecyclerView.setAdapter(mAdapter);
43         mAdapter.setOnItemClickListener(new OnRecyclerViewItemClickListener() {
44             @Override
45             public void onItemClick(View view, String data) {
46                 Toast.makeText(MainActivity.this, data, 1).show();
47             }
48         });
49     }
50 
51 }

这里使用到的自定义分割线类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     }

(6)部署程序到手机上,如下:

 

 

6. 总结:

在ListView中我们是调用ListView的setOnItemClickListener:

1 mListView.setOnItemClickListener(new OnItemClickListener() {
2             public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
3                  
4                     ...           
5                 
6             }
7         });

而在我们这里是调用mAdapter的setOnItemClickListener。且回调方法public void onItemClick()的参数也不一致,ListView中有被点击item的position参数,而我们这里直接是被点击item的相关数据(这里只是一个字符串)。

 

以上是关于Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件的主要内容,如果未能解决你的问题,请参考以下文章

Android 高级UI设计笔记18:实现圆角图片

Android 高级UI设计笔记19:PopupWindow使用详解

Android Kotlin:null 不能转换为非 null 类型 com.android.app.ui.category.CategoryAdapter.ViewHolder 想要在 recycl

微信团队讲课笔记 Android 开发UI设计

Android高级UI--Material Design设计理念下的控件使用详解与各种问题的解决方案

JavaScript高级程序设计(第三版)学习笔记8910章