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的相關數據(這里只是一個字符串)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM