## (一).前言:
Base-Adater-Helper是对我们传统的BaseAdapter的ViewHolder的模式的一个抽象封装,主要的功能可以让我们简化的书写AbsListView,例如ListView,GridView的自定义Adapter的代码,上一篇我们已经对该项目的基本使用做了介绍实例,今天我们来对该项目的实现详解源码分析一下,同时我们可以对此框架进行扩展开发。
FastDev4Android框架项目地址:[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
基本使用方式如下:
![](https://box.kancloud.cn/2016-01-18_569c8eb7ebc67.jpg)
我们看一下它的实例使用方法:
~~~
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()了。
其他一些工具方法,例如设置字体,文本,图片,颜色,事件啊等等….
![](https://box.kancloud.cn/2016-01-18_569c8eb81e568.jpg)
一大堆的方法这边就不贴代码了,具体每个方法的含义已经在项目源代码中注释了,大家有兴趣可以去下载项目然后去阅读一下:
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最新版本侧滑界面效果(三十八)