Android 解析RecyclerView(1)——带点击事件监听的通用Adapter
在之前我已经写过一篇关于设计通用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;
}
}
}
运行效果如下所示:
在上面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);
运行效果如下所示:
这里提供代码下载:解析RecyclerView(1)——带点击事件监听的通用Adapter

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
iOS真机调试遇到No such file or directory的问题
iOS开发连接到iPad上做真机调试,突然发生找不到文件或目录的错误,错误信息如下: No such file or directory (/Users/dudaniel/Library/Developer/Xcode/DerivedData/HelloWorldDevCamp-aopffacvzvkblqbavytvdpbwrucz/Build/Products/Debug-iphoneos/HelloWorldDevCamp.app/HelloWorldDevCamp) 从iPad上看,程序已经安装成功了,但是没有正常启动。 放狗搜了一下,解决办法就是重启一下xcode ,还不行的话就reset一下iPad。具体为什么会有这个错误还不清楚,如果您知道,肯赐教否? 作者: 峻祁连 邮箱:junqilian@163.com 出处: http://junqilian.cnblogs.com 转载请保留此信息。 本文转自峻祁连. Moving to Cloud/Mobile博客园博客,原文链接:http://www.cnblogs.com/junqilian/archive/2013/06...
-
下一篇
Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView
RecyclerView是用来替代ListView的一个控件,比ListView更加的简洁高效,不过也有一些比较不足的地方,比如:无法直接设置点击事件监听; 无法像ListView那样直接添加顶部View和底部View。 设置点击事件监听在前一篇文章已经解决了,这一篇文章要来介绍如何为RecyclerView添加顶部View和底部View。 一、源码分析 先来看下ListView的源码,研究它是如何添加顶部View的 public void addHeaderView(View v, Object data, boolean isSelectable) { final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); mAreAllItemsSelectable &= isSelectable; // Wrap the adapter if it wasn'...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- MySQL数据库在高并发下的优化方案