您现在的位置是:首页 > 文章详情

Android 解析RecyclerView(1)——带点击事件监听的通用Adapter

日期:2017-06-06点击:249

在之前我已经写过一篇关于设计通用Adapter的文章了:Android RecyclerView设计通用Adapter

按照最原始的做法,构建一个RecyclerView Adapter需要写挺多代码的,而通过对Java泛型的使用,可以使代码量变得只需十几行即可。此外,由于RecyclerView不像ListView那样支持直接添加顶部View,也不支持直接添加点击事件监听,需要开发者自己通过改造RecyclerView或者RecyclerView Adapter来完成需求。
我打算通过写三篇文章来对设计“带点击事件监听的通用Adapter”和“带顶部View和底部View的RecyclerView”进行介绍。

本文是对设计通用Adapter的介绍

一、ViewHolder

在使用RecyclerView的过程中,需要用到ViewHolder,在当中对控件进行初始化并赋予数据
为了避免每次加RecyclerView的子项时都要对View进行查找,可以使用SparseArray来存储View,key值是View ID,Value值则是View

 //用来存放View以减少findViewById的次数 private SparseArray<View> viewSparseArray; 

则每次查找View时,先从缓存中查找,如果缓存中存在则直接返回View,否则还是进行findViewById,找到了后再把View存入缓存
利用泛型也可以使返回的View自动转型

 /** * 根据 ID 来获取 View * * @param viewId viewID * @param <T> 泛型 * @return 将结果强转为 View 或 View 的子类型 */ private <T extends View> T getView(int viewId) { // 先从缓存中找,找到的话则直接返回 // 如果找不到则findViewById,再把结果存入缓存中 View view = viewSparseArray.get(viewId); if (view == null) { view = itemView.findViewById(viewId); viewSparseArray.put(viewId, view); } return (T) view; } 

由于我们使用到的控件一般都是TextView和ImageView,所以默认提供的操作View的方法也是针对这两个控件,可以根据实际情况再来增加

 public CommonRecyclerHolder setText(int viewId, CharSequence text) { TextView tv = getView(viewId); tv.setText(text); return this; } public CommonRecyclerHolder setImageResource(int viewId, int resourceId) { ImageView imageView = getView(viewId); imageView.setImageResource(resourceId); return this; } public CommonRecyclerHolder setImageResource(int viewId, Bitmap bitmap) { ImageView imageView = getView(viewId); imageView.setImageBitmap(bitmap); return this; } public CommonRecyclerHolder setViewVisibility(int viewId, int visibility) { getView(viewId).setVisibility(visibility); return this; } 

为了使RecyclerView支持点击事件监听,可以在ViewHolder的构造函数中对其进行事件监听设置。首先需要定义一个通用的点击事件监听接口,支持短按点击和长按点击

 public interface onClickCommonListener { void onClickListener(int position); void onLongClickListener(int position); } 

在View的真实点击事件监听函数中回调自定义的监听接口

private onClickCommonListener clickCommonListener; public CommonRecyclerHolder(View itemView) { super(itemView); viewSparseArray = new SparseArray<>(); itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } public void setClickCommonListener(onClickCommonListener clickCommonListener) { this.clickCommonListener = clickCommonListener; } @Override public void onClick(View view) { if (clickCommonListener != null) { clickCommonListener.onClickListener(getAdapterPosition()); } } @Override public boolean onLongClick(View view) { if (clickCommonListener != null) { clickCommonListener.onLongClickListener(getAdapterPosition()); } return true; } 

总的代码如下所示:

/** * 通用ViewHolder * Created by ZY on 2017/6/3. */ public class CommonRecyclerHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { public interface onClickCommonListener { void onClickListener(int position); void onLongClickListener(int position); } private onClickCommonListener clickCommonListener; //用来存放View以减少findViewById的次数 private SparseArray<View> viewSparseArray; public CommonRecyclerHolder(View itemView) { super(itemView); viewSparseArray = new SparseArray<>(); itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } public void setClickCommonListener(onClickCommonListener clickCommonListener) { this.clickCommonListener = clickCommonListener; } @Override public void onClick(View view) { if (clickCommonListener != null) { clickCommonListener.onClickListener(getAdapterPosition()); } } @Override public boolean onLongClick(View view) { if (clickCommonListener != null) { clickCommonListener.onLongClickListener(getAdapterPosition()); } return true; } /** * 根据 ID 来获取 View * * @param viewId viewID * @param <T> 泛型 * @return 将结果强转为 View 或 View 的子类型 */ private <T extends View> T getView(int viewId) { // 先从缓存中找,找到的话则直接返回 // 如果找不到则findViewById,再把结果存入缓存中 View view = viewSparseArray.get(viewId); if (view == null) { view = itemView.findViewById(viewId); viewSparseArray.put(viewId, view); } return (T) view; } public CommonRecyclerHolder setText(int viewId, CharSequence text) { TextView tv = getView(viewId); tv.setText(text); return this; } public CommonRecyclerHolder setImageResource(int viewId, int resourceId) { ImageView imageView = getView(viewId); imageView.setImageResource(resourceId); return this; } public CommonRecyclerHolder setImageResource(int viewId, Bitmap bitmap) { ImageView imageView = getView(viewId); imageView.setImageBitmap(bitmap); return this; } public CommonRecyclerHolder setViewVisibility(int viewId, int visibility) { getView(viewId).setVisibility(visibility); return this; } } 

二、RecyclerView Adapter

接下来需要是来改造 RecyclerView Adapter了

在有些时候,有让RecyclerView的子项呈现不同的布局的需求,这就需要我们在以下方法中使不同子项返回不同的viewType值了

 public int getItemViewType(int position) 

然后,根据不同子项的viewType在以下方法中加载不同的布局文件

 public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) 

为了使我们自定义的RecyclerView Adapter支持多布局,需要使用到一个接口来返回不同的布局文件

 //多布局支持 public interface MultiTypeSupport<T> { int getLayoutId(T item, int position); } 

使用泛型来对应不同情况下需要RecyclerView展示的数据集合

 private List<T> dataList; 

则 RecyclerView Adapter几个需要重写的方法可以如下定义

@Override public int getItemViewType(int position) { if (multiTypeSupport != null) { return multiTypeSupport.getLayoutId(dataList.get(position), position); } return super.getItemViewType(position); } @Override public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (multiTypeSupport != null) { layoutId = viewType; } View view = layoutInflater.inflate(layoutId, parent, false); return new CommonRecyclerHolder(view); } @Override public void onBindViewHolder(CommonRecyclerHolder holder, int position) { bindData(holder, dataList.get(position)); holder.setClickCommonListener(clickCommonListener); } @Override public int getItemCount() { return dataList.size(); } 

声明一个抽象方法,使子类根据实际情况来进行数据绑定

 protected abstract void bindData(CommonRecyclerHolder holder, T data); 

为了对应多种情况,比如有时候需要使用到多个布局文件,有时候又需要设置点击事件监听,所以可以声明多个构造函数来对应所有的情况

 /** * 私有构造函数 * * @param context 上下文 * @param dataList 数据集合 */ private CommonRecyclerAdapter(Context context, List<T> dataList) { this.layoutInflater = LayoutInflater.from(context); this.dataList = dataList; } /** * 适用于:列表所有的子项都使用相同的布局文件,且不需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param layoutId 布局文件ID */ protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId) { this(context, dataList); this.layoutId = layoutId; } /** * 适用于:列表的子项使用不同的布局文件,且不需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param multiTypeSupport 支持多个布局文件 */ protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport) { this(context, dataList); this.multiTypeSupport = multiTypeSupport; } /** * 适用于:列表所有的子项都使用相同的布局文件,且需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param layoutId 布局文件ID * @param clickCommonListener 点击事件监听 */ protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId, CommonRecyclerHolder.onClickCommonListener clickCommonListener) { this(context, dataList, layoutId); this.clickCommonListener = clickCommonListener; } /** * 适用于:列表的子项使用不同的布局文件,且需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param multiTypeSupport 支持多个布局文件 * @param clickCommonListener 点击事件监听 */ protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport, CommonRecyclerHolder.onClickCommonListener clickCommonListener) { this(context, dataList, multiTypeSupport); this.clickCommonListener = clickCommonListener; } 

总的代码如下所示:

/** * 通用RecyclerView Adapter * Created by ZY on 2017/6/3. */ public abstract class CommonRecyclerAdapter<T> extends RecyclerView.Adapter<CommonRecyclerHolder> { //多布局支持 public interface MultiTypeSupport<T> { int getLayoutId(T item, int position); } private MultiTypeSupport<T> multiTypeSupport; private LayoutInflater layoutInflater; private List<T> dataList; private int layoutId; private CommonRecyclerHolder.onClickCommonListener clickCommonListener; /** * 私有构造函数 * * @param context 上下文 * @param dataList 数据集合 */ private CommonRecyclerAdapter(Context context, List<T> dataList) { this.layoutInflater = LayoutInflater.from(context); this.dataList = dataList; } /** * 适用于:列表所有的子项都使用相同的布局文件,且不需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param layoutId 布局文件ID */ protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId) { this(context, dataList); this.layoutId = layoutId; } /** * 适用于:列表的子项使用不同的布局文件,且不需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param multiTypeSupport 支持多个布局文件 */ protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport) { this(context, dataList); this.multiTypeSupport = multiTypeSupport; } /** * 适用于:列表所有的子项都使用相同的布局文件,且需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param layoutId 布局文件ID * @param clickCommonListener 点击事件监听 */ protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId, CommonRecyclerHolder.onClickCommonListener clickCommonListener) { this(context, dataList, layoutId); this.clickCommonListener = clickCommonListener; } /** * 适用于:列表的子项使用不同的布局文件,且需要监听点击事件 * * @param context 上下文 * @param dataList 数据集合 * @param multiTypeSupport 支持多个布局文件 * @param clickCommonListener 点击事件监听 */ protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport, CommonRecyclerHolder.onClickCommonListener clickCommonListener) { this(context, dataList, multiTypeSupport); this.clickCommonListener = clickCommonListener; } @Override public int getItemViewType(int position) { if (multiTypeSupport != null) { return multiTypeSupport.getLayoutId(dataList.get(position), position); } return super.getItemViewType(position); } @Override public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (multiTypeSupport != null) { layoutId = viewType; } View view = layoutInflater.inflate(layoutId, parent, false); return new CommonRecyclerHolder(view); } @Override public void onBindViewHolder(CommonRecyclerHolder holder, int position) { bindData(holder, dataList.get(position)); holder.setClickCommonListener(clickCommonListener); } @Override public int getItemCount() { return dataList.size(); } protected abstract void bindData(CommonRecyclerHolder holder, T data); } 

三、实际使用

首先定义一个Java Bean,方便进行数据传递

/** * Created by ZY on 2017/6/3. */ public class Data { private int imageResource; private String hintText; public Data(int imageResource, String hintText) { this.imageResource = imageResource; this.hintText = hintText; } public int getImageResource() { return imageResource; } public String getHintText() { return hintText; } } 

然后定义一个 item1.xml 和 item2.xml 布局文件,来作为RecyclerView的子项要显示的布局文件

item1.xml 的布局代码如下所示, item2.xml 只是更改了背景颜色而已,就不贴出来了

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/iv_head" android:layout_width="50dp" android:layout_height="50dp" android:layout_margin="5dp" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/tv_hintText" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="17sp" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#abc" /> </LinearLayout> 

现在我们定义Adapter子类需要写的代码量就很少了,根据实际情况声明需要的构造函数即可,不需要每个都写出来

/** * Created by ZY on 2017/6/3. */ public class MyCommonRecyclerAdapter extends CommonRecyclerAdapter<Data> { public MyCommonRecyclerAdapter(Context context, List<Data> dataList, int layoutId) { super(context, dataList, layoutId); } public MyCommonRecyclerAdapter(Context context, List<Data> dataList, MultiTypeSupport<Data> multiTypeSupport) { super(context, dataList, multiTypeSupport); } public MyCommonRecyclerAdapter(Context context, List<Data> dataList, int layoutId, CommonRecyclerHolder.onClickCommonListener clickCommonListener) { super(context, dataList, layoutId, clickCommonListener); } public MyCommonRecyclerAdapter(Context context, List<Data> dataList, MultiTypeSupport<Data> multiTypeSupport, CommonRecyclerHolder.onClickCommonListener clickCommonListener) { super(context, dataList, multiTypeSupport, clickCommonListener); } @Override protected void bindData(CommonRecyclerHolder holder, Data data) { holder.setImageResource(R.id.iv_head, data.getImageResource()) .setText(R.id.tv_hintText, data.getHintText()); } } 

然后,在Activity中声明使用

Activity的布局文件如下所示,两个按钮分别用来增添和删除数据

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.czy.demo.MainActivity"> <Button android:id="@+id/btn_addData" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="增添数据" /> <Button android:id="@+id/btn_deleteData" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn_addData" android:text="删除数据" /> <android.support.v7.widget.RecyclerView android:id="@+id/rv_singleDataList" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/btn_deleteData" /> </RelativeLayout> 

Activity的所有代码如下所示:

public class SingleLayoutActivity extends AppCompatActivity implements CommonRecyclerHolder.onClickCommonListener, View.OnClickListener { private List<Data> dataList; private MyCommonRecyclerAdapter myCommonRecyclerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_layout); dataList = new ArrayList<>(); initData(); RecyclerView rv_singleDataList = (RecyclerView) findViewById(R.id.rv_singleDataList); rv_singleDataList.setLayoutManager(new LinearLayoutManager(this)); myCommonRecyclerAdapter = new MyCommonRecyclerAdapter(this, dataList, R.layout.item, this); rv_singleDataList.setAdapter(myCommonRecyclerAdapter); findViewById(R.id.btn_addData).setOnClickListener(this); findViewById(R.id.btn_deleteData).setOnClickListener(this); } private void initData() { for (int i = 0; i < 50; i++) { Data data = new Data(R.mipmap.ic_launcher_round, "Hi:" + i); dataList.add(data); } } @Override public void onClickListener(int position) { Toast.makeText(this, "点击:" + position, Toast.LENGTH_SHORT).show(); } @Override public void onLongClickListener(int position) { Toast.makeText(this, "长按:" + position, Toast.LENGTH_SHORT).show(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_addData: Data data = new Data(R.mipmap.ic_launcher, "Hi"); dataList.add(data); myCommonRecyclerAdapter.notifyDataSetChanged(); break; case R.id.btn_deleteData: if (dataList.size() > 0) { dataList.remove(0); } myCommonRecyclerAdapter.notifyDataSetChanged(); break; } } } 

运行效果如下所示:

img_5e395f54365fe36d49f349ed2874374d.gif
这里写图片描述

在上面RecyclerView使用的是相同的布局文件,可以在声明myCommonRecyclerAdapter变量时使用另一个构造函数即可支持显示多布局

 MyCommonRecyclerAdapter myCommonRecyclerAdapter = new MyCommonRecyclerAdapter(this, dataList, new CommonRecyclerAdapter.MultiTypeSupport<Data>() { @Override public int getLayoutId(Data item, int position) { if (position % 2 == 0) { return R.layout.item; } return R.layout.item2; } }, this); 

运行效果如下所示:


img_b10eff617639d0a6fb946166876775e1.gif
这里写图片描述

这里提供代码下载:解析RecyclerView(1)——带点击事件监听的通用Adapter

原文链接:https://yq.aliyun.com/articles/649714
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章