为RecyclerView打造通用Adapter 让RecyclerView更加好用

article/2025/11/10 15:36:51

转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/47251585;
本文出自:【张鸿洋的博客】

一、概述

记得好久以前针对ListView类控件写过一篇打造万能的ListView GridView 适配器,如今RecyclerView异军突起,其Adapter的用法也与ListView类似,那么我们也可以一步一步的为其打造通用的Adapter,使下列用法书写更加简单:

  • 简单的数据绑定(单种Item)
  • 多种Item Type 数据绑定
  • 增加onItemClickListener , onItenLongClickListener
  • 优雅的添加分类header

二、使用方式和效果图

在一步一步完成前,我们先看下使用方式和效果图:

(1)简单的数据绑定

首先看我们最常用的单种Item的书写方式:

mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
{@Overridepublic void convert(ViewHolder holder, String s){holder.setText(R.id.id_item_list_title, s);}
});

是不是相当方便,在convert方法中完成数据、事件绑定即可。

(2)多种ItemViewType

多种ItemViewType,正常考虑下,我们需要根据Item指定ItemType,并且根据ItemType指定相应的布局文件。我们通过MultiItemTypeSupport完成指定:

MultiItemTypeSupport  multiItemSupport = new MultiItemTypeSupport<ChatMessage>()
{@Overridepublic int getLayoutId(int itemType){//根据itemType返回item布局文件id}@Overridepublic int getItemViewType(int postion, ChatMessage msg){//根据当前的bean返回item type}
}

剩下就简单了,将其作为参数传入到MultiItemCommonAdapter即可。

mRecyclerView.setAdapter(new SectionAdapter<String>(this, mDatas, multiItemSupport)
{@Overridepublic void convert(ViewHolder holder, String s){holder.setText(R.id.id_item_list_title, s);}
});

贴个效果图:

(3)添加分类header

其实属于多种ItemViewType的一种了,只是比较常用,我们就简单封装下。

依赖正常考虑下,这种方式需要额外指定header的布局,以及布局中显示标题的TextView了,以及根据Item显示什么样的标题。我们通过SectionSupport对象指定:

SectionSupport<String> sectionSupport = new SectionSupport<String>()
{@Overridepublic int sectionHeaderLayoutId(){return R.layout.header;}@Overridepublic int sectionTitleTextViewId(){return R.id.id_header_title;}@Overridepublic String getTitle(String s){return s.substring(0, 1);}
};

3个方法,一个指定header的布局文件,一个指定布局文件中显示title的TextView,最后一个用于指定显示什么样的标题(根据Adapter的Bean)。

接下来就很简单了:

mRecyclerView.setAdapter(new SectionAdapter<String>(this, R.layout.item_list, mDatas, sectionSupport)
{@Overridepublic void convert(ViewHolder holder, String s){holder.setText(R.id.id_item_list_title, s);}
});

这样就完了,效果图如下:

ok,看完上面简单的介绍,相信你已经基本了解了,没错,和我上篇ListView万能Adapter的使用方式基本一样,并且已经封装到同一个库了,链接为:https://github.com/hongyangAndroid/base-adapter,此外还提供了ItemClick,ItemLongClick,添加EmptyView等支持。

说了这么多,下面进入正题,看我们如何一步步完成整个封装的过程。

三、通用的ViewHolder

RecyclerView要求必须使用ViewHolder模式,一般我们在使用过程中,都需要去建立一个新的ViewHolder然后作为泛型传入Adapter。那么想要建立通用的Adapter,必须有个通用的ViewHolder。

首先我们确定下ViewHolder的主要的作用,实际上是通过成员变量存储对应的convertView中需要操作的字View,避免每次findViewById,从而提升运行的效率。

那么既然是通用的View,那么对于不同的ItemType肯定没有办法确定创建哪些成员变量View,取而代之的只能是个集合来存储了。

那么代码如下:

public class ViewHolder extends RecyclerView.ViewHolder
{private SparseArray<View> mViews;private View mConvertView;private Context mContext;public ViewHolder(Context context, View itemView, ViewGroup parent){super(itemView);mContext = context;mConvertView = itemView;mViews = new SparseArray<View>();}public static ViewHolder get(Context context, ViewGroup parent, int layoutId){View itemView = LayoutInflater.from(context).inflate(layoutId, parent,false);ViewHolder holder = new ViewHolder(context, itemView, parent, position);return holder;}/*** 通过viewId获取控件** @param viewId* @return*/public <T extends View> T getView(int viewId){View view = mViews.get(viewId);if (view == null){view = mConvertView.findViewById(viewId);mViews.put(viewId, view);}return (T) view;}
}

代码很简单,我们的ViewHolder继承自RecyclerView.ViewHolder,内部通过SparseArray来缓存我们itemView内部的子View,从而得到一个通用的ViewHolder。每次需要创建ViewHolder只需要传入我们的layoutId即可。

ok,有了通用的ViewHolder之后,我们的通用的Adapter分分钟就出来了。

四、通用的Adapter

我们的每次使用过程中,针对的数据类型Bean肯定是不同的,那么这里肯定要引入泛型代表我们的Bean,内部通过一个List代表我们的数据,ok,剩下的看代码:

package com.zhy.base.adapter.recyclerview;import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;import com.zhy.base.adapter.ViewHolder;import java.util.List;/*** Created by zhy on 16/4/9.*/
public abstract class CommonAdapter<T> extends RecyclerView.Adapter<ViewHolder>
{protected Context mContext;protected int mLayoutId;protected List<T> mDatas;protected LayoutInflater mInflater;public CommonAdapter(Context context, int layoutId, List<T> datas){mContext = context;mInflater = LayoutInflater.from(context);mLayoutId = layoutId;mDatas = datas;}@Overridepublic ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType){ViewHolder viewHolder = ViewHolder.get(mContext, parent, mLayoutId);return viewHolder;}@Overridepublic void onBindViewHolder(ViewHolder holder, int position){holder.updatePosition(position);convert(holder, mDatas.get(position));}public abstract void convert(ViewHolder holder, T t);@Overridepublic int getItemCount(){return mDatas.size();}
}

继承自RecyclerView.Adapter,需要复写的方法还是比较少的。首先我们使用过程中传输我们的数据集mDatas,和我们item的布局文件layoutId。

onCreateViewHolder时,通过layoutId即可利用我们的通用的ViewHolder生成实例。

onBindViewHolder这里主要用于数据、事件绑定,我们这里直接抽象出去,让用户去操作。可以看到我们修改了下参数,用户可以拿到当前Item所需要的对象和viewHolder去操作。

那么现在用户的使用是这样的:

mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
{@Overridepublic void convert(ViewHolder holder, String s){TextView tv = holder.getView(R.id.id_item_list_title);tv.setText(s);}
});

看到这里,爽了很多,目前我们仅仅写了很少的代码,但是我们的通用的Adapter感觉已经初步完成了。

可以看到我们这里通过viewholder根据控件的id拿到控件,然后再进行数据绑定和事件操作,我们还能做些什么简化呢?

恩,我们可以通过一些辅助方法简化我们的代码,所以继续往下看。

五、进一步封装ViewHolder

我们的Item实际上使用的控件较多时候可能都是TextView,ImageView等,我们一般在convert方法都是去设置文本,图片什么的,那么我们可以在ViewHolder里面,写上如下的一些辅助方法:

class ViewHolder extends RecyclerView.Adapter<ViewHolder>
{//...public ViewHolder setText(int viewId, String text){TextView tv = getView(viewId);tv.setText(text);return this;}public ViewHolder setImageResource(int viewId, int resId){ImageView view = getView(viewId);view.setImageResource(resId);return this;}public ViewHolder setOnClickListener(int viewId,View.OnClickListener listener){View view = getView(viewId);view.setOnClickListener(listener);return this;}
}

当然上面只给出了几个方法,你可以把常用控件的方法都写进去,并且在使用过程中不断完善即可。

有了一堆辅助方法后,我们的操作更加简化了一步。

mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
{@Overridepublic void convert(ViewHolder holder, String s){//TextView tv = holder.getView(R.id.id_item_list_title);//tv.setText(s);holder.setText(R.id.id_item_list_title,s);}
});

ok,到这里,我们的针对单种ViewItemType的通用Adapter就完成了,代码很简单也很少,但是简化效果非常明显。

ok,接下来我们考虑多种ItemViewType的情况。

六、多种ItemViewType

多种ItemViewType,一般我们的写法是:

  • 复写getItemViewType,根据我们的bean去返回不同的类型
  • onCreateViewHolder中根据itemView去生成不同的ViewHolder

如果大家还记得,我们的ViewHolder是通用的,唯一依赖的就是个layoutId。那么上述第二条就变成,根据不同的itemView告诉我用哪个layoutId即可,生成viewholder这种事我们通用adapter来做。

于是,引入一个接口:

public interface MultiItemTypeSupport<T>
{int getLayoutId(int itemType);int getItemViewType(int position, T t);
}

可以很清楚的看到,这个接口实际就是完成我们上述的两条工作。用户在使用过程中,通过实现上面两个方法,指明不同的Bean返回什么itemViewType,不同的itemView所对应的layoutId.

ok,有了上面这个接口,我们的参数就够了,下面开始我们的MultiItemCommonAdapter的编写。

public abstract class MultiItemCommonAdapter<T> extends CommonAdapter<T>
{protected MultiItemTypeSupport<T> mMultiItemTypeSupport;public MultiItemCommonAdapter(Context context, List<T> datas,MultiItemTypeSupport<T> multiItemTypeSupport){super(context, -1, datas);mMultiItemTypeSupport = multiItemTypeSupport;}@Overridepublic int getItemViewType(int position){return mMultiItemTypeSupport.getItemViewType(position, mDatas.get(position));}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){int layoutId = mMultiItemTypeSupport.getLayoutId(viewType);ViewHolder holder = ViewHolder.get(mContext, parent, layoutId;return holder;}}

几乎没有几行代码,感觉简直不需要消耗脑细胞。getItemViewType用户的传入的MultiItemTypeSupport.getItemViewType完成,onCreateViewHolder中根据MultiItemTypeSupport.getLayoutId返回的layoutId,去生成ViewHolder即可。

ok,这样的话,我们的多种ItemViewType的支持也就完成了,一路下来感觉还是蛮轻松的~~~

最后,我们还有个添加分类的header,为什么想起来封装这个呢,这个是因为我看到了这么个项目:https://github.com/ragunathjawahar/simple-section-adapter,这个项目给了种类似装饰者模式的方法,为ListView添加了header,有兴趣可以看下。我想我们的RecylerView也来个吧,不过我们这里直接通过继承Adapter完成。

七、添加分类Header

话说添加分类header,其实就是我们多种ItemViewType的一种,那么我们需要知道哪些参数呢?

简单思考下,我们需要:

  1. header所对应的布局文件
  2. 显示header的title对应的TextView
  3. 显示的title是什么(一般肯定根据Bean生成)

ok,这样的话,我们依然引入一个接口,用于提供上述3各参数

public interface SectionSupport<T>
{public int sectionHeaderLayoutId();public int sectionTitleTextViewId();public String getTitle(T t);
}

方法名应该很明确了,这里引入泛型,对应我们使用时的数据类型Bean。

刚才也说了我们的分类header是多种ItemViewType的一种,那么直接继承MultiItemCommonAdapter实现。

public abstract class SectionAdapter<T> extends MultiItemCommonAdapter<T>
{private SectionSupport mSectionSupport;private static final int TYPE_SECTION = 0;private LinkedHashMap<String, Integer> mSections;private MultiItemTypeSupport<T> headerItemTypeSupport = new MultiItemTypeSupport<T>(){@Overridepublic int getLayoutId(int itemType){if (itemType == TYPE_SECTION)return mSectionSupport.sectionHeaderLayoutId();elsereturn mLayoutId;}@Overridepublic int getItemViewType(int position, T o){return mSections.values().contains(position) ?TYPE_SECTION :1;}};@Overridepublic int getItemViewType(int position){return mMultiItemTypeSupport.getItemViewType(position, null);}final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver(){@Overridepublic void onChanged(){super.onChanged();findSections();}};public SectionAdapter(Context context, int layoutId, List<T> datas, SectionSupport sectionSupport){super(context, datas, null);mLayoutId = layoutId;mMultiItemTypeSupport = headerItemTypeSupport;mSectionSupport = sectionSupport;mSections = new LinkedHashMap<>();findSections();registerAdapterDataObserver(observer);}@Overrideprotected boolean isEnabled(int viewType){if (viewType == TYPE_SECTION)return false;return super.isEnabled(viewType);}@Overridepublic void onDetachedFromRecyclerView(RecyclerView recyclerView){super.onDetachedFromRecyclerView(recyclerView);unregisterAdapterDataObserver(observer);}public void findSections(){int n = mDatas.size();int nSections = 0;mSections.clear();for (int i = 0; i < n; i++){String sectionName = mSectionSupport.getTitle(mDatas.get(i));if (!mSections.containsKey(sectionName)){mSections.put(sectionName, i + nSections);nSections++;}}}@Overridepublic int getItemCount(){return super.getItemCount() + mSections.size();}public int getIndexForPosition(int position){int nSections = 0;Set<Map.Entry<String, Integer>> entrySet = mSections.entrySet();for (Map.Entry<String, Integer> entry : entrySet){if (entry.getValue() < position){nSections++;}}return position - nSections;}@Overridepublic void onBindViewHolder(ViewHolder holder, int position){position = getIndexForPosition(position);if (holder.getItemViewType() == TYPE_SECTION){holder.setText(mSectionSupport.sectionTitleTextViewId(), mSectionSupport.getTitle(mDatas.get(position)));return;}super.onBindViewHolder(holder, position);}
}

根据我们之前的代码,使用MultiItemCommonAdapter,需要提供一个MultiItemTypeSupport,我们这里当然也不例外。可以看到上述代码,我们初始化了成员变量headerItemTypeSupport,分别对getLayoutIdgetItemViewType进行了实现。

  • getLayoutId如果type是header类型,则返回mSectionSupport.sectionHeaderLayoutId();否则则返回mLayout.
  • getItemViewType根据位置判断,如果当前是header所在位置,返回header类型常量;否则返回1.

ok,可以看到我们构造方法中调用了findSections(),主要为了存储我们的title和对应的position,通过一个MapmSections来存储。

那么对应的getItemCount()方法,我们多了几个title肯定总数会增加,所以需要复写。

onBindViewHolder中我们有一行重置position的代码,因为我们的position变大了,所以在实际上绑定我们数据时,这个position需要还原,代码逻辑见getIndexForPosition(position)

最后一点就是,每当我们的数据发生变化,我们的title集合,即mSections就可能会发生变化,所以需要重新生成,本来准备复写notifyDataSetChanged方法,在里面重新生成,没想到这个方法是final的,于是利用了registerAdapterDataObserver(observer);,在数据发生变化回调中重新生成,记得在onDetachedFromRecyclerView里面对注册的observer进行解注册。

ok,到此我们的增加Header就结束了~~

恩,上面是针对普通的Item增加header的代码,如果是针对多种ItemViewType呢?其实也很简单,这种方式需要传入MultiItemTypeSupport。那么对于headerItemTypeSupport中的getItemViewType等方法,不是header类型时,交给传入的MultiItemTypeSupport即可,大致的代码如下:

headerItemTypeSupport = new MultiItemTypeSupport<T>()
{@Overridepublic int getLayoutId(int itemType){if (itemType == TYPE_SECTION)return mSectionSupport.sectionHeaderLayoutId();elsereturn multiItemTypeSupport.getLayoutId(itemType);}@Overridepublic int getItemViewType(int position, T o){int positionVal = getIndexForPosition(position);return mSections.values().contains(position) ?TYPE_SECTION :multiItemTypeSupport.getItemViewType(positionVal, o);}
};

那么这样的话,今天的博客就结束了,有几点需要说明下:

本来是想接着以前的万能Adapter后面写,但是为了本文的独立和完整性,还是尽可能没有去依赖上篇博客的内容了。

此外,文章最后给出的开源代码与上述代码存在些许的差异,因为开源部分源码整合了ListView,RecyclerView等,而本文上述代码完全针对RecyclerView进行编写。

对于ItemClick,ItemLongClick的代码就不赘述了,其实都是通过itemView.setXXXListener完成,详细的参考代码即可。

更多详细的内容以及代码,参见https://github.com/hongyangAndroid/base-adapter


欢迎关注我的微博:
http://weibo.com/u/3165018720


微信公众号:hongyangAndroid
(欢迎关注,第一时间推送博文信息)


http://chatgpt.dhexx.cn/article/qf1ZQxJ5.shtml

相关文章

RecyclerView的基本用法

RecyclerView 是一个增强版的ListView&#xff0c;不仅可以实现和ListView同样的效果&#xff0c;还优化了ListView中存在的各种不足之处 ResyslerView 能够实现横向滚动&#xff0c;这是ListView所不能实现的 目前官方更加推荐使用RecyclerView. 1.实现垂直方向的滚动 在 …

RecyclerView安卓androidx.widget.RecyclerView

导入RecyclerView implementation com.android.support:recyclerview-v7:28.0.0或者把常用的组件都导入了&#xff0c;包括了RecyclerView implementation com.android.support:design:28.0.01.RecyclerView介绍 RecyclerView它可以说是一个增强版的ListView。ListView由于其…

RecyclerView的基本使用

RecyclerView是Android一个更强大的控件,其不仅可以实现和ListView同样的效果,还有优化了ListView中的各种不足。其可以实现数据纵向滚动,也可以实现横向滚动(ListView做不到横向滚动)。接下来讲解RecyclerView的用法。 RecyclerView 基本用法 因为RecyclerView属于新增的控件,…

RecyclerView 的使用(androidx)

文章目录 一、导包二、基本使用三、设置分割线四、自定义点击事件五、实现 GridView六、实现瀑布流七、更多效果八、RecyclerView 常见问题九、RecyclerView 和 ScrollView 嵌套的问题 一、导包 implementation androidx.recyclerview:recyclerview:1.1.0二、基本使用 2.1、首…

RecyclerView简单使用(非常详细)

星期六&#xff0c;又是撸代码的一天 作为一个初级都算不上的小白&#xff0c;一步一个脚印的学吧&#xff0c;学一个记一个 今天记录的是RecyclerView RecyclerView 简述创建布局添加 RecyclerView和每个list布局&#xff08;偏新手向&#xff0c;选择性跳过&#xff09; 创建…

androidx.recyclerview:recyclerview的使用

添加扩展 或手动修改app/build.gradle&#xff1a; 在dependencies里添加 implementation androidx.recyclerview:recyclerview:1.0.0新建布局layout item布局fruit_item.xml&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayo…

Android学习之RecyclerView的使用

RecyclerView是Android 5.0推出的&#xff0c;是support-v7包中的新组件,它被用来代替ListView和GridView&#xff0c;并且能够实现瀑布流的布局&#xff0c;更加高级并且更加灵活&#xff0c;提供更为高效的回收复用机制&#xff0c;同时实现管理与视图的解耦合。 官方文档解释…

RecyclerView剖析

####简介   本文将从RecyclerView实现原理并结合源码详细分析这个强大的控件。阅读本文要求&#xff1a;1、熟悉android控件绘制&#xff0c;2、了解动画&#xff0c;3、了解Scroller.本文所示源码版本是23.2.0。 ####基本使用   RecyclerView的基本使用并不复杂&#xff…

RecyclerView的使用(一)

目录 1.RecyclerView概述 2.RecyclerView的简单使用 3.改变布局管理器&#xff0c;RecyclerView的变化 1.RecyclerView概述 在谷歌Android官网&#xff0c;给RecyclerView的描述是: 那RecyclerView凭什么要比ListView要更高级&#xff1f;更灵活&#xff1f; 答案是&#x…

RecyclerView详解

RecyclerView 简称 RV&#xff0c; 是作为 ListView 和 GridView 的加强版出现的&#xff0c;目的是在有限的屏幕之上展示大量的内容&#xff0c;因此 RecyclerView 的复用机制的实现是它的一个核心部分。 RV 常规使用方式如下&#xff1a; 解释说明。 setLayoutManager&…

RecyclerView(三)—— RecyclerView的缓存机制

RecyclerView内存优越性&#xff0c;得益于它独特的缓存机制。 1 如何复用表项 如果列表中的每个表项在移出屏幕时被销毁&#xff0c;移入时又被重新创建&#xff0c;是很消耗资源&#xff0c;所以RecyclerView引入了缓存机制。缓存是为了复用&#xff0c;复用的好处是有可能…

RecyclerView详解一,使用及缓存机制

本文大致会先讲解RecyclerView的基础知识及使用&#xff0c;最后会深入讲解一点原理。当然&#xff0c;本人知识水平有限哈&#xff0c;太深入的东西我现在还没接触到&#xff0c;还请大家包容&#xff0c;阿里嘎多~ 一、RecyclerView的历史与发展 既然讲到了RV&#xff0c;那…

Android开发—RecyclerView使用

1.RecyclerView是什么 RecyclerView 在Android中用于创建列表。 官网的解释为&#xff1a; RecyclerView 可以让您轻松高效地显示大量数据。您提供数据并定义每个列表项的外观&#xff0c;而 RecyclerView 库会根据需要动态创建元素。 当RecyclerView的列表项滚出屏幕的时候&a…

Android RecyclerView使用简述

RecyclerView使用简述 前言正文一、创建项目二、RecyclerView基本使用① item布局和适配器② 显示数据③ 添加Item点击事件④ 添加Item子控件点击事件⑤ 添加长按事件⑥ 多个子控件点击事件 三、RecyclerView ViewBinding使用① 适配器② 显示数据③ 添加控件点击和长按 四、R…

es6新特性总结及使用说明

目录 简介 新特性说明 let语法 const语法 解构赋值 模板字符串 对象简写 对象操作--深拷贝 箭头函数 小结 简介 1. ECMAScript 6.0是 JavaScript 语言的下一代标准&#xff0c; 2015 年 6 月发布。 ES6 设计目标是达到 JavaScript 语言可以用来编写复杂的大型程序&a…

ES6有哪些新特性

ES6有哪些新特性(1)变量声明:由var变为let和const; (2)模板字符串:使用反引号;在模板字符串里面支持换行,并可以在里面使用${}来包裹一个变量或表达式; (3)解构:有数组解构和对象解构;可以快速获取数组和对象的值; (4) 展开运算符:在ES6中用…来表示展开运算符,它可以将数组…

ES6 新特性知识点总结

文章目录 ES6let及const解构赋值模板字符串Symbol类型Set和Map数据结构箭头函数类 ES6 ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 ES6 实际上是一个泛指&#xff0c;泛指 ES2015 及后续的版本。 每一次标准的诞生都意味着语言的…

ES6 新特性

1 、ES6 新特性 现在使用主流的前端框架中&#xff0c;如ReactJS、Vue.js、angularjs等&#xff0c;都会使用到ES6的新特性&#xff0c;作为一名高级工程师而言&#xff0c;ES6也就成为了必修课&#xff0c;所以本套课程先以ES6的新特性开始。 1.1、了解ES6 ES6&#xff0c;…

ES6新特性总结-面试必会

文章目录 一、let和const二、Symbol三、模板字符串3.1 什么是模板字符串3.2 字符串新方法 四、解构表达式4.1 数组解构4.2 对象解构 五、Set()、map()数据结构5.1 map()是什么及写法&#xff1f;5.1.1 map()是什么及写法&#xff1f;5.1.2 map()下的内置方法&#xff1a; 5.2 S…

JavaScript ES6新特性

JavaScript ES6带来了新的语法和特性&#xff0c;使得代码更加的现代和可读。它包括许多重要功能&#xff0c;如箭头函数、模板字符串、解构赋值等等。 const 和 let const 是 ES6 中用于声明变量的新关键字。const 比 var 强大。一旦使用&#xff0c;变量就不能重新分配。换…