## (一).前言:
Base-Adater-Helper是對我們傳統的BaseAdapter的ViewHolder的模式的一個抽象封裝,主要的功能可以讓我們簡化的書寫AbsListView,例如ListView,GridView的自定義Adapter的代碼,上一篇我們已經對該項目的基本使用做了介紹實例,今天我們來對該項目的實現詳解源碼分析一下,同時我們可以對此框架進行擴展開發。
FastDev4Android框架項目地址:[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
基本使用方式如下:

我們看一下它的實例使用方法:
~~~
mAdapter = newQuickAdapter<ModuleBean>(this, R.layout.lv_item_base_layout,moduleBeans){
@Override
protected voidconvert(BaseAdapterHelper helper, ModuleBean item) {
//列表底下顯示進度
mAdapter.showIndeterminateProgress(true);
helper.setText(R.id.text_lv_item_title, item.getModulename())
.setText(R.id.text_lv_item_description, item.getDescription())
.setImageUrl(R.id.img_lv_item, item.getImgurl());
}
};
lv_base_adapter.setAdapter(mAdapter);
~~~
## (二).總體分析:
整個項目其實比較簡單也就是四個主要的類:
* BaseAdapterHelper
* BaseQuickAdapter
* EnhancedQuickAdapter
* QuickAdapter
通過閱讀整個項目源代碼之后發現,當前實現也是基于ViewHolder模式的,等會我
們分析就知道了。其中view類型相關的采用泛型存儲,最重要的數據綁定工作,采用抽象函數convert()實現,全部交給用戶來實現自定義的綁定。
從上面的基本使用中發現,我們在使用該base-adapter-helper過程中,只需要創建newQuickAdapter()傳入布局,數據,控件和數據模型綁定即可。但是我們查看QuickAdapter類的代碼發現:
~~~
packagecom.chinaztt.fda.adapter.base;
importandroid.content.Context;
importandroid.view.View;
importandroid.view.ViewGroup;
importjava.util.List;
import staticcom.chinaztt.fda.adapter.base.BaseAdapterHelper.get;
/**
* Abstraction class of a BaseAdapter in whichyou only need
* to provide the convert()implementation.<br/>
* Using the provided BaseAdapterHelper, yourcode is minimalist.
* @param <T> The type of the items inthe list.
*/
public abstractclass QuickAdapter<T> extends BaseQuickAdapter<T,BaseAdapterHelper> {
/**
* Create a QuickAdapter.
* @param context The context.
* @param layoutResId The layout resourceid of each item.
*/
public QuickAdapter(Context context, intlayoutResId) {
super(context, layoutResId);
}
/**
* Same asQuickAdapter#QuickAdapter(Context,int) but with
* some initialization data.
* @param context The context.
* @param layoutResId The layout resourceid of each item.
* @param data A new list is created out of this oneto avoid mutable list
*/
public QuickAdapter(Context context, intlayoutResId, List<T> data) {
super(context, layoutResId, data);
}
/**
* 進行獲取類ViewHolder的 BaseAdapterHelper
* @param position The position of the item within theadapter's data set of the item whose view we want.
* @param convertView The old view toreuse, if possible. Note: You should check that this view
* is non-null and of anappropriate type before using. If it is not possible to convert
* this view to display thecorrect data, this method can create a new view.
* Heterogeneous lists canspecify their number of view types, so that this View is
* always of the right type(see {@link #getViewTypeCount()} and
* {@link#getItemViewType(int)}).
* @param parent The parent that this view will eventuallybe attached to
* @return
*/
protected BaseAdapterHelpergetAdapterHelper(int position, View convertView, ViewGroup parent) {
return get(context, convertView,parent, layoutResId, position);
}
}
~~~
其實它并沒有做什么其他更多的時候,就只有兩個構造方法,和一個獲取BaseAdapterHelper對象的方法。所以要研究該框架,我們只需要研究它的父類BaseQuickAdapter和BaseAdapterHelper類即可。最后簡單的看一下EnhancedQuickAdapter類。
2.1.BaseQuickAdapter類:該類繼承了BaseAdapter類,同時實現了BaseAdapter中通用的幾個抽象方法(也就是我們平時自定義Adapter需要實現的幾個方法),完成了Adapter要做的絕大多數操作以及對于Data操作的方法(不過個人趕腳用處不是特別大哈~個人見解)。該類還有兩個泛型數據,其中T代表數據,H針對BaseAdapterHelper。
~~~
packagecom.chinaztt.fda.adapter.base;
importandroid.content.Context;
importandroid.view.Gravity;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.BaseAdapter;
importandroid.widget.FrameLayout;
importandroid.widget.ProgressBar;
importjava.util.ArrayList;
importjava.util.List;
/**
* Abstraction class of a BaseAdapter in whichyou only need
* to provide the convert()implementation.<br/>
* Using the provided BaseAdapterHelper, yourcode is minimalist.
* @param <T> The type of the items inthe list.
*/
public abstractclass BaseQuickAdapter<T, H extends BaseAdapterHelper> extendsBaseAdapter {
protected static final String TAG =BaseQuickAdapter.class.getSimpleName();
//上下文引用
protected final Context context;
//需要顯示的布局id
protected final int layoutResId;
//需要顯示的數據
protected final List<T> data;
//是否顯示進度
protected booleandisplayIndeterminateProgress = false;
/**
* Create a QuickAdapter.
* @param context The context.
* @param layoutResId The layout resourceid of each item.
*/
public BaseQuickAdapter(Context context,int layoutResId) {
this(context, layoutResId, null);
}
/**
* Same asQuickAdapter#QuickAdapter(Context,int) but with
* some initialization data.
* @param context The context.
* @param layoutResId The layout resourceid of each item.
* @param data A new list is created out of this oneto avoid mutable list
*/
public BaseQuickAdapter(Context context,int layoutResId, List<T> data) {
this.data = data == null ? newArrayList<T>() : new ArrayList<T>(data);
this.context = context;
this.layoutResId = layoutResId;
}
/**
* 判斷是否需要顯示進度,如果顯示 數量+1
* @return
*/
@Override
public int getCount() {
int extra =displayIndeterminateProgress ? 1 : 0;
return data.size() + extra;
}
/**
* 判斷索引是否大于等于數據長度,如果等于,最后一個item應該為進度,那么返回的數據對象為null即可
* @param position
* @return
*/
@Override
public T getItem(int position) {
if (position >= data.size()) returnnull;
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* view類型返回2,這邊還需要顯示進度bar
* @return
*/
@Override
public int getViewTypeCount() {
return 2;
}
/**
* 進行判斷索引是不是已經大于等于數據的長度
* 如果超過或者等于數據的長度 返回1
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return position >= data.size() ? 1 :0;
}
@Override
public View getView(int position, ViewconvertView, ViewGroup parent) {
if (getItemViewType(position) == 0) {
//獲取適配器helper --相當于獲取ViewHolder-BaseAdapterHelper
final H helper =getAdapterHelper(position, convertView, parent);
//獲取item model 數據
T item = getItem(position);
//給子類QuickAdapter來進行實現,不過QuickAdapter也是抽象類,給具體創建的類進行實現
convert(helper, item);
helper.setAssociatedObject(item);
return helper.getView();
}
//顯示進度
returncreateIndeterminateProgressView(convertView, parent);
}
/**
* 創建進度條 顯示在view的結尾
* @param convertView
* @param parent
* @return
*/
private ViewcreateIndeterminateProgressView(View convertView, ViewGroup parent) {
if (convertView == null) {
FrameLayout container = newFrameLayout(context);
container.setForegroundGravity(Gravity.CENTER);
ProgressBar progress = newProgressBar(context);
container.addView(progress);
convertView = container;
}
return convertView;
}
@Override
public boolean isEnabled(int position) {
return position < data.size();
}
//====================================================
//下面的方法基本封裝了操作集合相關的
//主要為新增add,設置set,移除remove以及替換,是否存在判斷
//=====================================================
public void add(T elem) {
data.add(elem);
notifyDataSetChanged();
}
public void addAll(List<T> elem) {
data.addAll(elem);
notifyDataSetChanged();
}
public void set(T oldElem, T newElem) {
set(data.indexOf(oldElem), newElem);
}
public void set(int index, T elem) {
data.set(index, elem);
notifyDataSetChanged();
}
public void remove(T elem) {
data.remove(elem);
notifyDataSetChanged();
}
public void remove(int index) {
data.remove(index);
notifyDataSetChanged();
}
public void replaceAll(List<T> elem){
data.clear();
data.addAll(elem);
notifyDataSetChanged();
}
public boolean contains(T elem) {
return data.contains(elem);
}
/**
* 清空數組
*/
/** Clear data list */
public void clear() {
data.clear();
notifyDataSetChanged();
}
public voidshowIndeterminateProgress(boolean display) {
if (display ==displayIndeterminateProgress) return;
displayIndeterminateProgress = display;
notifyDataSetChanged();
}
/**
* 實現該方法,讓用戶自己綁定控件和數據
* Implement this method and use the helperto adapt the view to the given item.
* @param helper A fully initializedhelper.
* @param item The item that needs to be displayed.
*/
protected abstract void convert(H helper, Titem);
/**
* You can override this method to use acustom BaseAdapterHelper in order to fit your needs
* @param position The position of the item within theadapter's data set of the item whose view we want.
* @param convertView The old view toreuse, if possible. Note: You should check that this view
* is non-null and of anappropriate type before using. If it is not possible to convert
* this view to display thecorrect data, this method can create a new view.
* Heterogeneous lists canspecify their number of view types, so that this View is
* always of the right type(see {@link #getViewTypeCount()} and
* {@link#getItemViewType(int)}).
* @param parent The parent that this view will eventuallybe attached to
* @return An instance of BaseAdapterHelper
*/
protected abstract H getAdapterHelper(intposition, View convertView, ViewGroup parent);
}
~~~
重點我們來看下這個類的相關實現:
2.1.1.成員變量context為獲取控件所需要的上下文,layoutResId為布局文件的id。其中data的類型是List的,由于這邊傳入的實體信息可能是不同類型的集合,所以這邊采用了泛型來進行定義了。
2.1.2.getViewTypeCount(),getItemViewType()這邊我們可以看源代碼第一個函數返回2,第二個函數會進行判斷返回position。因為base-adapter-helper已經實現的progressbar進度的顯示控制條。
2.1.3.然后是一些實現adapter都要實現的方法例如:getCount,getView,getItem,getItemId之類的方法,還有一些關于數據操作的方法。
2.1.4.重點看一下getView方法:
~~~
public ViewgetView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == 0) {
//獲取適配器helper --相當于獲取ViewHolder-BaseAdapterHelper
final H helper =getAdapterHelper(position, convertView, parent);
//獲取item model 數據
T item = getItem(position);
//給子類QuickAdapter來進行實現,不過QuickAdapter也是抽象類,給具體創建的類進行實現
convert(helper, item);
helper.setAssociatedObject(item);
return helper.getView();
}
//顯示進度
returncreateIndeterminateProgressView(convertView, parent);
}
~~~
重點地方已經注釋了,在這個方法中,會首先進行判斷getItemViewType(position)的值,如果返回值不等于0的時候,那就是說顯示底部的進度,其他的情況會正常加載view。此時通過抽象方法getAdapterHelper()來進行獲取一個BaseAdapterHelper。
該抽象方法的實現類是QuickAdapter:
~~~
/**
* 進行獲取類ViewHolder的 BaseAdapterHelper
* @param position The position of the item within theadapter's data set of the item whose view we want.
* @param convertView The old view toreuse, if possible. Note: You should check that this view
* is non-null and of anappropriate type before using. If it is not possible to convert
* this view to display thecorrect data, this method can create a new view.
* Heterogeneous lists canspecify their number of view types, so that this View is
* always of the right type(see {@link #getViewTypeCount()} and
* {@link#getItemViewType(int)}).
* @param parent The parent that this view will eventuallybe attached to
* @return
*/
protected BaseAdapterHelpergetAdapterHelper(int position, View convertView, ViewGroup parent) {
return get(context, convertView,parent, layoutResId, position);
}
~~~
其中又去調用BaseAdapterHelper的如下方法:
~~~
/** This method ispackage private and should only be used by QuickAdapter. */
/**
* 進行獲取類ViewHolder的BaseAdapterHelper對象
* @param context 上下文引用
* @param convertView item view
* @param parent 父控件view
* @param layoutId 布局ID
* @param position 索引
* @return
*/
static BaseAdapterHelper get(Contextcontext, View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return newBaseAdapterHelper(context, parent, layoutId, position);
}
// Retrieve the existing helper andupdate its position
BaseAdapterHelper existingHelper =(BaseAdapterHelper) convertView.getTag();
existingHelper.position = position;
return existingHelper;
}
~~~
該類中的實現方法就是首先判斷convertView是否為null,如果為null,進行創建。否則直接從getTag()進行獲取,然后返回當前BaseAdapterHelper,到這邊相比大家有沒有明白BaseAdapterHelper和我們以前接觸的ViewHolder相似了呢?對的,這邊的BaseAdapterHelper其實就是充當的ViewHolder角色。
上面我們分析了getAdapterHelper的生成方式,下面我們看一下itemdata的獲取,這個比較簡單直接如下即可:
//獲取item model數據? ??
~~~
T item = getItem(position);
~~~
該類最后我們來看一個最關鍵的代碼
//給子類QuickAdapter來進行實現,不過QuickAdapter也是抽象類,給具體創建的類進行實現
~~~
convert(helper, item);
~~~
???????????該convert()為抽象方法,convert的參數為BaseAdapterHelper和實體對象item,讓我們用戶可以自定義自由綁定數據到控件中。綁定成功之后直接通過調用helper.getView()返回即可。
2.2.BaseAdapterHelper類
上面get()方法的時候已經說過BaseAdapterHelper相當于ViewHolder,同時該類還提供一大堆的方法來讓我們進行設置view的數據,屬性,以及一些事件方法。?我們首先開看構造方法:?????????
~~~
protectedBaseAdapterHelper(Context context, ViewGroup parent, int layoutId, intposition) {
this.context = context;
this.position = position;
this.views = newSparseArray<View>();
convertView =LayoutInflater.from(context) //根據布局id來加載view
.inflate(layoutId, parent,false);
convertView.setTag(this);//相當于存放ViewHolder
}
~~~
該構造方法創建一個稀疏數組來存放view對象(用來綁定數據的)。然后根據布局id來進行獲取convertView,同時把當前對象加入到convertView得tag中,然后后面我們可以通過convertView.getTag()來進行獲取,這樣保持BaseAdapterHelper對于convertView的相互持有引用。
除了以上的構造方法以外,還有另外一個進入BaseAdapterHelper的入口:
~~~
/**
* This method is the only entry point toget a BaseAdapterHelper.
* @param context The current context.
* @param convertView The convertView argpassed to the getView() method.
* @param parent The parent arg passed to the getView()method.
* @return A BaseAdapterHelper instance.
*/
public static BaseAdapterHelper get(Contextcontext, View convertView, ViewGroup parent, int layoutId) {
return get(context, convertView,parent, layoutId, -1);
}
/** This method is package private andshould only be used by QuickAdapter. */
/**
* 進行獲取類ViewHolder的BaseAdapterHelper對象
* @param context 上下文引用
* @param convertView item view
* @param parent 父控件view
* @param layoutId 布局ID
* @param position 索引
* @return
*/
static BaseAdapterHelper get(Contextcontext, View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return newBaseAdapterHelper(context, parent, layoutId, position);
}
// Retrieve the existing helper andupdate its position
BaseAdapterHelper existingHelper =(BaseAdapterHelper) convertView.getTag();
existingHelper.position = position;
return existingHelper;
}
~~~
上面會進行判斷convertView是否存在,不存在進行調用構造方法創建,存在直接從convertView.getTag()來獲取BaseAdapterHelper對象。
下面我們來另外的比較重要的兩個方法,主要是獲取view控件,然后同時加入到稀疏數組中:
~~~
public <T extends View> T getView(intviewId) {
return retrieveView(viewId);
}
~~~
~~~
/**
* 進行從模板view中獲取相應的控件 然后放入到view控件集合中
*/
protected <T extends View> TretrieveView(int viewId) {
//從集合中獲取當前viewId的view,如果集合中不存在,那么從view重findById()
//獲取出來存放到集合中 并且返回
View view = views.get(viewId);
if (view == null) {
view =convertView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
~~~
上面方法我們可以看出,每次調用retrieveView方法的時候,都會根據viewId去views集合中查詢一下,如果不存在,那么會通過findViewById(viewId)來進行獲取,同時把該view加入到集合中,這樣下次查詢就不需要重復調用findViewById()了。
其他一些工具方法,例如設置字體,文本,圖片,顏色,事件啊等等….

一大堆的方法這邊就不貼代碼了,具體每個方法的含義已經在項目源代碼中注釋了,大家有興趣可以去下載項目然后去閱讀一下:
FastDev4Android框架項目地址:[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
2.3.EnhancedQuickAdapter類:該類是繼承自QuickAdapter,其他多出下面的兩個方法:
~~~
/**
* 進行綁定控件和數據 數據發生變化
* @param helper A fully initializedhelper.
* @param item The item that needs to be displayed.
*/
@Override
protected final voidconvert(BaseAdapterHelper helper, T item) {
boolean itemChanged =helper.associatedObject == null || !helper.associatedObject.equals(item);
helper.associatedObject = item;
convert(helper, item, itemChanged);
}
/**
* 讓用戶來自定義
* @param helper The helper to use to adapt the view.
* @param item The item you should adapt the view to.
* @param itemChanged Whether or not thehelper was bound to another object before.
*/
protected abstract voidconvert(BaseAdapterHelper helper, T item, boolean itemChanged);
~~~
仔細看convert()方法,多了第三方參數itemChanged,對于data集合發生改變的時候刷新View。
## (三).改進點:
我們在進行控件,數據模型進行綁定的時候都是采用set方法才完成。但是我們的需求是千變萬化的,就拿圖片加載來說,框架內部默認是采用Picasso來加載圖片的。但是如果我們的項目想要采用Volley,UIL或者Fresco呢?那么我們不得不需要在里邊進行擴展相應的方法來實現了。雖然這種方案可以解決問題,但是如果還有其他方案,那么一直在擴展就會變得很龐大了。幸運的時候convert()方法回調回來的BaseAdapterHelper對象helper,該類中有getView()和retrieveView()方法,同時getView()還是public,對外開放的,我們在convert()實現方法中直接通過getView()即可獲取view,然后如何顯示圖片就看我們自己的了。除此之外里邊很多設置字體,文本等等很少輔助類,如果需要設置更多的東西,我們需要不斷的擴展。
## (四).結束語:
經過昨天和今天的講解介紹,相應大家對于base-adapter-helper的基本使用和源碼原理分析有一個比較清晰的了解,以后在項目中也可以多多使用這個,一定會提高寫代碼的效率。其他這個框架學習分析下來,非常核心的東西也還是比較好理解,重點要看思考,分析吧。繼續加油。
到此Base-Adapter-Helper的基本介紹和基本使用已經講完了,具體實例和框架注釋過的全部代碼已經上傳到FastDev4Android項目中了。
同時歡迎大家去Github站點進行clone或者下載瀏覽:
[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)?同時歡迎大家star和fork整個開源快速開發框架項目~
- 前言
- Android快速開發框架介紹(一)
- Android首頁圖片自動無限循環輪播Gallery+FlowIndicator(二)
- Android 列表下拉刷新組件PullToRefreshListView使用(三)
- Android 數據緩存器ACache的詳解和使用(四)
- Android崩潰異常捕捉CustomCrash,提升用戶體驗(五)
- Android實現沉浸式狀態欄(六)
- AndroidAnnnotations注入框架介紹和Android Studios基本配置(七)
- AndroidAnnnotations注入框架的工作原理(八)
- AndroidAnnnotations注入框架使用之注入組件Components(九)
- AndroidAnnnotations注入框架使用之Injection標簽詳解(十)
- AndroidAnnnotations注入框架使用之事件綁定Event Binding(十一)
- AndroidAnnnotations注入框架使用之線程處理Threading(十二)
- AndroidAnnnotations注入框架使用之第三方框架集成RoboGuice(十三)
- AndroidAnnnotations注入框架使用之第三方框架集成Otto事件總線(十四)
- AndroidAnnnotations注入框架使用之第三方框架集成OrmLite(十五)
- AndroidAnnnotations注入框架使用之最佳實踐之Adapters和lists(十六)
- AndroidAnnnotations注入框架使用之最佳實踐SharedPreferences(十七)
- Android MVP開發模式詳解(十九)
- 消息總線EventBus的基本使用(二十)
- 消息總線EventBus源碼分析以及與Otto框架對比(二十一)
- 列表頭生成帶文本或者字母的圖片開源庫TextDrawable使用和詳解(二十二)
- 重寫WebView網頁加載以及JavaScript注入詳解(二十三)
- BaseAdapterHelper的基本使用介紹,讓你擺脫狂寫一堆Adapter煩惱(二十四)
- BaseAdapterHelper詳解源碼分析,讓你擺脫狂寫一堆Adapter煩惱(二十五)
- Volley完全解析之基礎使用(二十六)
- Volley完全解析之進階最佳實踐與二次封裝(二十七)
- RecyclerView完全解析,讓你從此愛上它(二十八)
- RecyclerView完全解析之打造新版類Gallery效果(二十九)
- RecyclerView完全解析之結合AA(Android Annotations)注入框架實例(三十)
- RecyclerView完全解析之下拉刷新與上拉加載SwipeRefreshLayout(三十一)
- CardView完全解析與RecyclerView結合使用(三十二)
- 神器ViewDragHelper完全解析,媽媽再也不擔心我自定義ViewGroup滑動View操作啦~(三十三)
- 神器ViewDragHelper完全解析之詳解實現QQ5.X側滑酷炫效果(三十四)
- 實例解析之SwipeRefreshLayout+RecyclerView+CardView(三十五)
- HorizontalScrollView,Fragment,FragmentStatePagerAdapter打造網易新聞Tab及滑動頁面效果(三十六)
- Android Design支持庫TabLayout打造仿網易新聞Tab標簽效果(三十七)
- 打造QQ6.X最新版本側滑界面效果(三十八)