原文出处: 张鸿洋 (Granker,@鸿洋_ )html
1、概述java
记得很久之前针对ListView类控件写过一篇打造万能的ListView GridView 适配器,现在RecyclerView异军突起,其Adapter的用法也与ListView相似,那么咱们也能够一步一步的为其打造通用的Adapter,使下列用法书写更加简单:android
简单的数据绑定(单种Item)git
多种Item Type 数据绑定github
增长onItemClickListener , onItenLongClickListener缓存
优雅的添加分类headeride
在一步一步完成前,咱们先看下使用方式和效果图:布局
首先看咱们最经常使用的单种Item的书写方式:post
Javathis
1 2 3 4 5 6 7 8 |
mRecyclerView.setAdapter(new CommonAdapter(this, R.layout.item_list, mDatas) { @Override public void convert(ViewHolder holder, String s) { holder.setText(R.id.id_item_list_title, s); } }); |
是否是至关方便,在convert方法中完成数据、事件绑定便可。
多种ItemViewType,正常考虑下,咱们须要根据Item指定ItemType,而且根据ItemType指定相应的布局文件。咱们经过MultiItemTypeSupport
完成指定:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
MultiItemTypeSupport multiItemSupport = new MultiItemTypeSupport() { @Override public int getLayoutId(int itemType) { //根据itemType返回item布局文件id }
@Override public int getItemViewType(int postion, ChatMessage msg) { //根据当前的bean返回item type } } |
剩下就简单了,将其做为参数传入到MultiItemCommonAdapter
便可。
Java
1 2 3 4 5 6 7 8 |
mRecyclerView.setAdapter(new SectionAdapter(this, mDatas, multiItemSupport) { @Override public void convert(ViewHolder holder, String s) { holder.setText(R.id.id_item_list_title, s); } }); |
贴个效果图:
其实属于多种ItemViewType的一种了,只是比较经常使用,咱们就简单封装下。
依赖正常考虑下,这种方式须要额外指定header的布局,以及布局中显示标题的TextView了,以及根据Item显示什么样的标题。咱们经过SectionSupport
对象指定:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
SectionSupport sectionSupport = new SectionSupport() { @Override public int sectionHeaderLayoutId() { return R.layout.header; }
@Override public int sectionTitleTextViewId() { return R.id.id_header_title; }
@Override public String getTitle(String s) { return s.substring(0, 1); } }; |
3个方法,一个指定header的布局文件,一个指定布局文件中显示title的TextView,最后一个用于指定显示什么样的标题(根据Adapter的Bean)。
接下来就很简单了:
Java
1 2 3 4 5 6 7 8 |
mRecyclerView.setAdapter(new SectionAdapter(this, R.layout.item_list, mDatas, sectionSupport) { @Override public 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等支持。
说了这么多,下面进入正题,看咱们如何一步步完成整个封装的过程。
RecyclerView要求必须使用ViewHolder模式,通常咱们在使用过程当中,都须要去创建一个新的ViewHolder而后做为泛型传入Adapter。那么想要创建通用的Adapter,必须有个通用的ViewHolder。
首先咱们肯定下ViewHolder的主要的做用,其实是经过成员变量存储对应的convertView中须要操做的字View,避免每次findViewById,从而提高运行的效率。
那么既然是通用的View,那么对于不一样的ItemType确定没有办法肯定建立哪些成员变量View,取而代之的只能是个集合来存储了。
那么代码以下:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class ViewHolder extends RecyclerView.ViewHolder { private SparseArray mViews; private View mConvertView; private Context mContext;
public ViewHolder(Context context, View itemView, ViewGroup parent) { super(itemView); mContext = context; mConvertView = itemView; mViews = new SparseArray(); }
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 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分分钟就出来了。
咱们的每次使用过程当中,针对的数据类型Bean确定是不一样的,那么这里确定要引入泛型表明咱们的Bean,内部经过一个List表明咱们的数据,ok,剩下的看代码:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
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 extends RecyclerView.Adapter { protected Context mContext; protected int mLayoutId; protected List mDatas; protected LayoutInflater mInflater;
public CommonAdapter(Context context, int layoutId, List datas) { mContext = context; mInflater = LayoutInflater.from(context); mLayoutId = layoutId; mDatas = datas; }
@Override public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) { ViewHolder viewHolder = ViewHolder.get(mContext, parent, mLayoutId); return viewHolder; }
@Override public void onBindViewHolder(ViewHolder holder, int position) { holder.updatePosition(position); convert(holder, mDatas.get(position)); }
public abstract void convert(ViewHolder holder, T t);
@Override public int getItemCount() { return mDatas.size(); } } |
继承自RecyclerView.Adapter
,须要复写的方法仍是比较少的。首先咱们使用过程当中传输咱们的数据集mDatas,和咱们item的布局文件layoutId。
onCreateViewHolder
时,经过layoutId便可利用咱们的通用的ViewHolder生成实例。
onBindViewHolder
这里主要用于数据、事件绑定,咱们这里直接抽象出去,让用户去操做。能够看到咱们修改了下参数,用户能够拿到当前Item所须要的对象和viewHolder去操做。
那么如今用户的使用是这样的:
Java
1 2 3 4 5 6 7 8 9 |
mRecyclerView.setAdapter(new CommonAdapter(this, R.layout.item_list, mDatas) { @Override public void convert(ViewHolder holder, String s) { TextView tv = holder.getView(R.id.id_item_list_title); tv.setText(s); } }); |
看到这里,爽了不少,目前咱们仅仅写了不多的代码,可是咱们的通用的Adapter感受已经初步完成了。
能够看到咱们这里经过viewholder根据控件的id拿到控件,而后再进行数据绑定和事件操做,咱们还能作些什么简化呢?
恩,咱们能够经过一些辅助方法简化咱们的代码,因此继续往下看。
咱们的Item实际上使用的控件较多时候可能都是TextView
,ImageView
等,咱们通常在convert方法都是去设置文本,图片什么的,那么咱们能够在ViewHolder里面,写上以下的一些辅助方法:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class ViewHolder extends RecyclerView.AdapterViewHolder> { //... 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; } } |
固然上面只给出了几个方法,你能够把经常使用控件的方法都写进去,而且在使用过程当中不断完善便可。
有了一堆辅助方法后,咱们的操做更加简化了一步。
Java
1 2 3 4 5 6 7 8 9 10 |
mRecyclerView.setAdapter(new CommonAdapter(this, R.layout.item_list, mDatas) { @Override public 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,通常咱们的写法是:
复写getItemViewType
,根据咱们的bean去返回不一样的类型
onCreateViewHolder
中根据itemView去生成不一样的ViewHolder
若是你们还记得,咱们的ViewHolder是通用的,惟一依赖的就是个layoutId。那么上述第二条就变成,根据不一样的itemView告诉我用哪一个layoutId便可,生成viewholder这种事咱们通用adapter来作。
因而,引入一个接口:
Java
1 2 3 4 5 6 |
public interface MultiItemTypeSupport { int getLayoutId(int itemType);
int getItemViewType(int position, T t); } |
能够很清楚的看到,这个接口实际就是完成咱们上述的两条工做。用户在使用过程当中,经过实现上面两个方法,指明不一样的Bean返回什么itemViewType,不一样的itemView所对应的layoutId.
ok,有了上面这个接口,咱们的参数就够了,下面开始咱们的MultiItemCommonAdapter
的编写。
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public abstract class MultiItemCommonAdapterT> extends CommonAdapterT> { protected MultiItemTypeSupport mMultiItemTypeSupport;
public MultiItemCommonAdapter(Context context, List datas, MultiItemTypeSupport multiItemTypeSupport) { super(context, -1, datas); mMultiItemTypeSupport = multiItemTypeSupport; }
@Override public int getItemViewType(int position) { return mMultiItemTypeSupport.getItemViewType(position, mDatas.get(position)); }
@Override public 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,其实就是咱们多种ItemViewType的一种,那么咱们须要知道哪些参数呢?
简单思考下,咱们须要:
header所对应的布局文件
显示header的title对应的TextView
显示的title是什么(通常确定根据Bean生成)
ok,这样的话,咱们依然引入一个接口,用于提供上述3各参数
Java
1 2 3 4 5 6 7 8 |
public interface SectionSupport { public int sectionHeaderLayoutId();
public int sectionTitleTextViewId();
public String getTitle(T t); } |
方法名应该很明确了,这里引入泛型,对应咱们使用时的数据类型Bean。
刚才也说了咱们的分类header是多种ItemViewType的一种,那么直接继承MultiItemCommonAdapter
实现。
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
public abstract class SectionAdapter extends MultiItemCommonAdapter { private SectionSupport mSectionSupport; private static final int TYPE_SECTION = 0; private LinkedHashMap mSections;
private MultiItemTypeSupport headerItemTypeSupport = new MultiItemTypeSupport() { @Override public int getLayoutId(int itemType) { if (itemType == TYPE_SECTION) return mSectionSupport.sectionHeaderLayoutId(); else return mLayoutId; } @Override public int getItemViewType(int position, T o) { return mSections.values().contains(position) ? TYPE_SECTION : 1; } };
@Override public int getItemViewType(int position) { return mMultiItemTypeSupport.getItemViewType(position, null); }
final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { super.onChanged(); findSections(); } };
public SectionAdapter(Context context, int layoutId, List datas, SectionSupport sectionSupport) { super(context, datas, null); mLayoutId = layoutId; mMultiItemTypeSupport = headerItemTypeSupport; mSectionSupport = sectionSupport; mSections = new LinkedHashMap(); findSections(); registerAdapterDataObserver(observer); }
@Override protected boolean isEnabled(int viewType) { if (viewType == TYPE_SECTION) return false; return super.isEnabled(viewType); }
@Override public 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 get(i));
if (!mSections.containsKey(sectionName)) { mSections.put(sectionName, i + nSections); nSections++; } }
}
@Override public int getItemCount() { return super.getItemCount() + mSections.size(); }
public int getIndexForPosition(int position) { int nSections = 0;
Set> entrySet = mSections.entrySet(); for (Map.Entry entry : entrySet) { if (entry.getValue() return position - nSections; }
@Override public 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
,分别对getLayoutId
和getItemViewType
进行了实现。
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
便可,大体的代码以下:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
headerItemTypeSupport = new MultiItemTypeSupport() { @Override public int getLayoutId(int itemType) { if (itemType == TYPE_SECTION) return mSectionSupport.sectionHeaderLayoutId(); else return multiItemTypeSupport.getLayoutId(itemType); }
@Override public 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进行编写。
QQ技术交流群290551701 http://cxy.liuzhihengseo.com/537.html