原文出處: 張鴻洋 (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