项目地址:https://github.com/JoanZapata/base-adapter-helper

1. 功能介绍

1.1. base-adapter-helper

base-adapter-helper 是对传统的 BaseAdapter ViewHolder 模式的一个封装。主要功能就是简化我们书写 AbsListView 的 Adapter 的代码,如 ListView,GridView。

1.2 基本使用

mListView.setAdapter(mAdapter = new QuickAdapter<Bean>(MainActivity.this, R.layout.item_list, mDatas) {

    @Override
protected void convert(BaseAdapterHelper helper, Bean item) {
helper.setText(R.id.tv_title, item.getTitle());
helper.setImageUrl(R.id.id_icon, item.getUrl());
helper.setText(R.id.tv_describe, item.getDesc());
helper.setText(R.id.tv_phone, item.getPhone());
helper.setText(R.id.tv_time, item.getTime());
}
});

1.3 长处

(1) 提供 QucikAdapter,省去类似 getCount() 等抽象函数的书写,仅仅需关注 Model 到 View 的显示。

(2) BaseAdapterHelper 中封装了大量用于为 View 操作的辅助方法,比如从网络载入图片:

helper.setImageUrl(R.id.iv_photo, item.getPhotoUrl());

1.4 缺点

(1) 与 Picasso 耦合,想替换为其它图片缓存须要改动源代码。

可通过接口方式。供三方依据自己的图片缓存库实现图片获取,或者直接去掉helper.setImageUrl(…)函数。

(2) 与内部加入的进度条偶尔,导致不支持多种类型布局

在本文最后给出不修改进度条的解决方法。更好的实现方式应该是通过接口方式暴露,供三方自己设置。

(3) 眼下的方案也不支持HeaderViewListAdapter

整体来说这个库比較简单,实现也有待改进。

2. 整体设计

因为 base-adapter-helper 本质上仍然是 ViewHolder 模式,以下各自是 base-adapter-helper 的整体设计图和 ViewHolder 模式的设计图,通过两图的比較。能够看出 base-adapter-helper 对传统的BaseAdapter进行了初步的实现(QuickAdapter),而且其子类仅需实现convert(…)方法,在convert(…)中能够拿到BaseAdapterHelper,BaseAdapterHelper就相当于ViewHolder。但其内部提供了大量的辅助方法。用于设置
View 上的数据及事件等。

base-adapter-helpr

ViewHolder Pattern

3. 具体设计

3.1 类关系图



这是 base-adapter-helper 库的主要类关系图。

(1) 在 BaseQucikAdapter 中实现了 BaseAdapter 中通用的抽象方法。

(2) BaseQuickAdapter 中两个泛型,当中 T 表示数据实体类(Bean)类型,H 表示 BaseAdapterHelper 或其子类。

(3) QucikAdapter 继承自 BaseQuickAdapter,而且传入 BaseAdapterHelper 作为 H 泛型;

(4) EnhancedQuickAdapter 主要为convert(…)方法加入了一个 itemChanged 參数。表示 item 相应数据是否发生变化;

(5) BaseAdapterHelper 为用于获取 View 并进行内容、事件设置等相关操作的辅助类。当中多数用于设置的方法都採用链式编程,方便书写。

(6) 能够依据自己须要继承 BaseAdapterHelper 来扩展,做为 BaseQuickAdapter 子类的 H 泛型。

3.2 核心类源代码分析

3.2.1 BaseQucikAdapter.java

该类继承自 BaseAdapter。完毕 BaseAdapter 中部分通用抽象方法的实现,类似ArrayAdapter

该类声明了两个泛型,当中 T 表示数据实体类(Bean)类型。H 表示 BaseAdapterHelper 或其子类,主要在扩展BaseAdapterHelper时使用。

(1) 构造方法
public BaseQuickAdapter(Context context, int layoutResId) {
this(context, layoutResId, null);
} public BaseQuickAdapter(Context context, int layoutResId, List<T> data) {
this.data = data == null ? new ArrayList<T>() : new ArrayList<T>(data);
this.context = context;
this.layoutResId = layoutResId;
}

Adapter 的必须元素 ItemView 的布局文件通过 layoutResId 指定,待展示数据通过 data 指定。

(2) 已经实现的主要方法
@Override
public int getCount() {
int extra = displayIndeterminateProgress ? 1 : 0;
return data.size() + extra;
} @Override
public int getViewTypeCount() {
return 2;
} @Override
public int getItemViewType(int position) {
return position >= data.size() ? 1 : 0;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == 0) {
final H helper = getAdapterHelper(position, convertView, parent);
T item = getItem(position);
helper.setAssociatedObject(item);
convert(helper, item);
return helper.getView();
} return createIndeterminateProgressView(convertView, parent);
}

上面列出了 BaseQucikAdapter 中已经实现的主要方法,跟一般 BaseAdapter 类似,我们重点看下面几个点:

a. 重写了getViewTypeCount()getItemViewType(),这里 type 为 2,通过getView(…)能够看出,主要是为了在 AbsListView 最后显示一个进度条。这里也暴露了一个弊端,无法支持多种 Item 样式的布局;

b. getView(…)方法的实现中首先通过抽象函数getAdapterHelper(…) 得到 BaseAdapterHelper 及 item,然后通过抽象函数convert(…)实现 View 和 数据的绑定。

这样BaseQucikAdapter子类仅仅须要实现抽象函数getAdapterHelper(…)convert(…)就可以。

(3) 待实现的抽象方法
protected abstract void convert(H helper, T item);

protected abstract H getAdapterHelper(int position, View convertView, ViewGroup parent);

a. convert(H helper, T item)

通过helper将 View 和 数据绑定。

helper參数表示 BaseQuickAdapter 或其子类。用于获取 View 并进行内容、事件设置等相关操作,由getAdapterHelper(…)函数返回;item表示相应的数据。

b. getAdapterHelper(int position, View convertView, ViewGroup parent)

返回 BaseQuickAdapter 或其子类,绑定 item,然后返回值传递给上面的convert(…)函数。

关于getAdapterHelper(…)的实现见以下QuickAdapter的介绍。

3.2.2 QucikAdapter.java

这个类继承自BaseQuickAdapter,没什么代码,主要用于提供一个可高速使用的 Adapter。

对于getAdapterHelper(…)函数直接返回了BaseAdapterHelper。普通情况下直接用此类作为 Adapter 就可以,如1.2 基本使用的演示样例。

但假设你扩展了BaseAdapterHelper,重写getAdapterHelper(…)函数将其返回。就可以实现自己的 Adapter。

3.2.3 EnhancedQuickAdapter.java

继承自QuickAdapter,不过为convert(…)加入了一个參数itemChanged。表示 item 相应数据是否发生变化。

@Override
protected final void convert(BaseAdapterHelper helper, T item) {
boolean itemChanged = helper.associatedObject == null || !helper.associatedObject.equals(item);
helper.associatedObject = item;
convert(helper, item, itemChanged);
} protected abstract void convert(BaseAdapterHelper helper, T item, boolean itemChanged);

能够看到它的实现是通过helper.associatedObjectequals()方法推断数据是否发生变化,associatedObject 即我们的 bean。

BaseQuickAdapter.getView(…)能够看到其赋值的代码。

3.2.4 BaseAdapterHelper.java

可用于获取 View 并进行内容设置等相关操作的辅助类,该类的功能有:

(1) 充当了 ViewHolder 角色,KV 形式保存 convertView 中子 View 的 id 及其引用,方便查找。

和 convertView 通过 tag 关联;

(2) 提供了一堆辅助方法,用于为子 View 设置内容、样式、事件等。

(1) 构造相关方法
protected BaseAdapterHelper(Context context, ViewGroup parent, int layoutId, int position) {
this.context = context;
this.position = position;
this.views = new SparseArray<View>();
convertView = LayoutInflater.from(context) //
.inflate(layoutId, parent, false);
convertView.setTag(this);
} public static BaseAdapterHelper get(Context context, View convertView, ViewGroup parent, int layoutId) {
return get(context, convertView, parent, layoutId, -1);
} /** This method is package private and should only be used by QuickAdapter. */
static BaseAdapterHelper get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return new BaseAdapterHelper(context, parent, layoutId, position);
} // Retrieve the existing helper and update its position
BaseAdapterHelper existingHelper = (BaseAdapterHelper) convertView.getTag();
existingHelper.position = position;
return existingHelper;
}

QuickAdapter中,通过上面的 5 个參数的静态函数get(…)得到BaseAdapterHelper的实例。

4 个參数的get(…)方法,仅仅是将 position 默认传入了 -1。即不关注 postion 方法。

这里能够对照下我们平时在getView中编写的 ViewHolder 模式的代码。在一般的 ViewHolder 模式中,先推断convertView是否为空:

a. 假设是,则通过LayoutInflater inflate 一个布局文件。然后新建 ViewHolder 存储布局中各个子 View,通过 tag 绑定该 ViewHolder 到convertView,返回我们的convertView

b. 否则直接得到 tag 中的 ViewHolder。

结合BaseQuickAdaptergetView(…)代码。看下 base-adapter-helper 的实现。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == 0) {
final H helper = getAdapterHelper(position, convertView, parent);
T item = getItem(position);
helper.setAssociatedObject(item);
convert(helper, item);
return helper.getView();
} return createIndeterminateProgressView(convertView, parent);
}

先利用getAdapterHelper(…)得到BaseAdapterHelper或其子类,对于QuickAdapter而言,这个函数直接调用上面BaseAdapterHelperget(…)函数。

我们能够看到相同是先推断convertView是否为空。以确定是否须要新建BaseAdapterHelper,否则从 tag
中获取更新 position 后重用。

在构造方法中 inflate 了一个布局作为convertView。而且保存 context 及 postion,将convertViewBaseAdapterHelper通过tag关联。

(2) 几个重要的方法

普通情况下,在我们重写BaseQuickAdapterconvert(…)时,须要得到 View。这时我们能够通过其入參BaseAdapterHelpergetView(int viewId)得到该View。代码例如以下:

public <T extends View> T getView(int viewId) {
return retrieveView(viewId);
} @SuppressWarnings("unchecked")
protected <T extends View> T retrieveView(int viewId) {
View view = views.get(viewId);
if (view == null) {
view = convertView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}

通过 viewId 去 views 中进行寻找,找到则返回,找不到则加入并返回。

views 是一个 SparseArray。key为 view id,value 为 view。缓存已经查找到的子 view。

每一个convertView与一个BaseAdapterHelper绑定。每一个BaseAdapterHelper中包括一个views属性。views中存储convertView的子 View 的引用。

(3) 辅助方法

普通情况下,通过getView(int viewId)拿到该View,然后进行赋值就能够了。

可是此库考虑:既然是拿到 View 然后赋值。不如直接提供一些赋值的辅助方法。于是产生了一堆类似setText(int viewId, String value)的代码,内部首先通过 viewId 找到该 View,转为TextView然后调用setText(value)。部分代码例如以下:

public BaseAdapterHelper setText(int viewId, String value) {
TextView view = retrieveView(viewId);
view.setText(value);
return this;
} public BaseAdapterHelper setImageResource(int viewId, int imageResId) {…} public BaseAdapterHelper setBackgroundRes(int viewId, int backgroundRes) {…} public BaseAdapterHelper setTextColorRes(int viewId, int textColorRes) {…} public BaseAdapterHelper setImageDrawable(int viewId, Drawable drawable) {…} public BaseAdapterHelper setImageUrl(int viewId, String imageUrl) {…} public BaseAdapterHelper setImageBitmap(int viewId, Bitmap bitmap) {…} @SuppressLint("NewApi")
public BaseAdapterHelper setAlpha(int viewId, float value) {…} public BaseAdapterHelper setVisible(int viewId, boolean visible) {…} public BaseAdapterHelper linkify(int viewId) {…} public BaseAdapterHelper setProgress(int viewId, int progress, int max) {…} public BaseAdapterHelper setRating(int viewId, float rating, int max) {…} public BaseAdapterHelper setTag(int viewId, int key, Object tag) {…} public BaseAdapterHelper setChecked(int viewId, boolean checked) {…} public BaseAdapterHelper setAdapter(int viewId, Adapter adapter) {…}
……

实现都是依据 viewId 找到 View。然后为 View 赋值的代码。

这里仅仅要注意下:setImageUrl(int viewId, String imageUrl) 这种方法,默认是通过Picasso去载入图片的,当然你能够更改成你项目中使用的图片载入框架 Volley,UIL 等,假设不希望继续耦合,可參考1.4 缺点的建议改法。

也能够为子 View 去设置一个事件监听,部分代码例如以下:

public BaseAdapterHelper setOnClickListener(int viewId, View.OnClickListener listener) {
View view = retrieveView(viewId);
view.setOnClickListener(listener);
return this;
} public BaseAdapterHelper setOnTouchListener(int viewId, View.OnTouchListener listener) {…} public BaseAdapterHelper setOnLongClickListener(int viewId, View.OnLongClickListener listener) {…}

这里只列出一些经常使用的方法,假设有些控件的方法这里没有封装。能够通过BaseAdapterHelper.getView(viewId)得到控件去操作,或者继承BaseAdapterHelper实现自己的BaseAdapterHelper

4. 杂谈

4.1 耦合严重

(1) 与 Picasso 耦合,想替换为其它图片缓存须要改动源代码

可通过新增接口方式,供三方自己依据自己的图片缓存库实现图片获取,或者直接去掉helper.setImageUrl(…)函数。

(2) 与内部加入的进度条耦合。导致不支持多种类型布局

在以下给出不修改进度条的解决方法。更好的实现方式应该是通过接口方式暴露,供三方自己设置。

整体来说这个库比較简单。实现也有待改进。

4.2 眼下的方案也不支持HeaderViewListAdapter

4.3 扩展多种 Item 布局

通过3.2.1 BaseQucikAdapter.java的分析,能够看出 base-adapter-helper 并不支持多种布局 Item 的情况。尽管大多数情况下一个种样式就可以。可是要是让我用这么简单的方式写 Adapter,忽然来个多种布局 Item 的 ListView 又要 按传统的方式去写,这反差就太大了。以下我们介绍。怎样在本库的基础上加入多布局 Item 的支持。

(1) 分析

对于多种布局的 Item,大家都清楚。须要去复写BaseAdaptergetViewTypeCount()getItemViewType()。而且须要在getView()里面进行推断并选取不同布局文件。不同的布局也须要採用不同的ViewHolder

我们能够在构造QucikAdapter时。去设置getViewTypeCount()getItemViewType()的值,进一步将其抽象为一个接口。提供几个方法,假设须要使用多种 Item 布局,进行设置就可以。

(2) 扩展
  • 加入接口 MultiItemTypeSupport

    public interface MultiItemTypeSupport<T> {
    
      int getLayoutId(int position, T t);
    
      int getViewTypeCount();
    
      int getItemViewType(int postion, T t);
    }
  • 分别在QuickAdapterBaseQuickAdapter中加入新的构造函数

BaseQuickAdapter新增构造函数例如以下:

protected MultiItemTypeSupport<T> multiItemSupport;

public BaseQuickAdapter(Context context, ArrayList<T> data,
MultiItemTypeSupport<T> multiItemSupport) {
this.multiItemSupport = multiItemSupport;
this.data = data == null ? new ArrayList<T>() : new ArrayList<T>(data);
this.context = context;
}

QuickAdapter 新增构造函数例如以下:

public QuickAdapter(Context context, ArrayList<T> data,
MultiItemTypeSupport<T> multiItemSupport) {
super(context, data, multiItemSupport);
}

同一时候肯定须要改写BaseQuickAdaptergetViewTypeCount()getItemViewType()以及getView()函数。

@Override
public int getViewTypeCount() {
return multiItemSupport != null ? (mMultiItemSupport.getViewTypeCount() + 1) : 2);
} @Override
public int getItemViewType(int position) {
if (position >= data.size()) {
return 0;
}
return (mMultiItemSupport != null) ?
mMultiItemSupport.getItemViewType(position, data.get(position)) : 1;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == 0) {
return createIndeterminateProgressView(convertView, parent);
}
final H helper = getAdapterHelper(position, convertView, parent);
T item = getItem(position);
helper.setAssociatedObject(item);
convert(helper, item);
return helper.getView();
}

为了保留其原本提供的加入滚动栏的功能,我们在其基础上进行改动。

  • 改写BaseAdapterHelper的构造方法

由于我们不同的布局,肯定要相应不同的ViewHolder,这里BaseAdapterHelper事实上就扮演了ViewHolder的角色。

我们的BaseAdapterHelper是在QuickAdaptergetAdapterHelper中构造的。改动后代码:

QuickAdapter

protected BaseAdapterHelper getAdapterHelper(int position,
View convertView, ViewGroup parent) { if (mMultiItemSupport != null){
return get(
context,
convertView,
parent,
mMultiItemSupport.getLayoutId(position, data.get(position)),
position);
} else {
return get(context, convertView, parent, layoutResId, position);
}
}

BaseAdapterHelperget方法也须要改动。

/** This method is package private and should only be used by QuickAdapter. */
static BaseAdapterHelper get(Context context, View convertView,
ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return new BaseAdapterHelper(context, parent, layoutId, position);
} // Retrieve the existing helper and update its position
BaseAdapterHelper existingHelper = (BaseAdapterHelper)convertView
.getTag(); if (existingHelper.layoutId != layoutId) {
return new BaseAdapterHelper(context, parent, layoutId, position);
} existingHelper.position = position;
return existingHelper;
}

我们在 helper 中存储了当前的 layoutId。假设 layoutId 不一致。则又一次创建。

(3) 測试

以下展示核心代码

mListView = (ListView) findViewById(R.id.id_lv_main);

MultiItemTypeSupport<ChatMessage> multiItemTypeSupport = new MultiItemTypeSupport<ChatMessage>() {
@Override
public int getLayoutId(int position, ChatMessage msg) {
return msg.isComMeg() ? R.layout.main_chat_from_msg : R.layout.main_chat_send_msg;
} @Override
public int getViewTypeCount() {
return 2;
} @Override
public int getItemViewType(int postion, ChatMessage msg) {
return msg.isComMeg() ? ChatMessage.RECIEVE_MSG : ChatMessage.SEND_MSG;
}
}; initDatas(); mAdapter = new QuickAdapter<ChatMessage>(ChatActivity.this, mDatas,
multiItemTypeSupport) {
@Override
protected void convert(BaseAdapterHelper helper, ChatMessage item) {
switch (helper.layoutId) {
case R.layout.main_chat_from_msg:
helper.setText(R.id.chat_from_content, item.getContent());
helper.setText(R.id.chat_from_name, item.getName());
helper.setImageResource(R.id.chat_from_icon, item.getIcon());
break;
case R.layout.main_chat_send_msg:
helper.setText(R.id.chat_send_content, item.getContent());
helper.setText(R.id.chat_send_name, item.getName());
helper.setImageResource(R.id.chat_send_icon, item.getIcon());
break;
}
}
}; mListView.setAdapter(mAdapter);

当遇到多种布局 Item 的时候,首先构造一个MultiItemTypeSupport接口对象,然后在convert中依据 layoutId。获取不同的布局进行设置。

贴张效果图:

Android万能适配器base-adapter-helper的源代码分析的更多相关文章

  1. Android进阶笔记10:Android 万能适配器

    1. Android 万能适配器      项目中Listview GridView几乎是必用的组件,Android也提供一套机制,为这些控件绑定数据,那就是Adapter.用起来虽然还不错,但每次都 ...

  2. Android进阶笔记09:Android 万能适配器

    1. Android 万能适配器      项目中Listview GridView几乎是必用的组件,Android也提供一套机制,为这些控件绑定数据,那就是Adapter.用起来虽然还不错,但每次都 ...

  3. Android万能适配器Adapter-android学习之旅(74)

    万能适配器的代码的github地址是https://github.com/fengsehng/CommonAdapter 万能适配器的代码的github地址是https://github.com/fe ...

  4. Android应用程序进程启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...

  5. Android实现多个倒计时优化与源代码分析

    由于之前有个项目需求是须要时时刻去更新UI倒计时,之前想到的,这简单嘛,用计时或者Handler就能够搞定,并且性能也不错,可是需求要ListView,什么,?大量的View都须要,那Handle处理 ...

  6. Android系统进程Zygote启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6768304 在Android系统中,所有的应用 ...

  7. Android应用程序安装过程源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6766010 Android系统在启动的过程中, ...

  8. Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处.尊重劳动成果] 1 背景 之所以写这一篇博客的原因是由于之前有写过一篇<Android应用setCont ...

  9. Android—万能ListView适配器

    ListView是开发中最常用的控件了,但是总是会写重复的代码,浪费时间又没有意义. 最近参考一些资料,发现一个万能ListView适配器,代码量少,节省时间,总结一下分享给大家. 首先有一个自定义的 ...

随机推荐

  1. QWidget类中默认是忽略inputMethodEvent事件(要获取输入的内容就必须使用这个事件)

    因为项目的需要以及主管的要求,准备将工程移植到Qt中,这样就可以比较容易的实现跨平台了.因为之前工程是在windows下开发的,第一个平台又是mobile所以除了底层框架之外其他的都是使用的windo ...

  2. Perl 面向对象编程的两种实现和比较:

    <pre name="code" class="html">https://www.ibm.com/developerworks/cn/linux/ ...

  3. Java返回类型泛型的用法小结

    Java返回类型泛型的用法小结 版权声明:本文为博主原创文章,未经博主允许不得转载. 关于Java泛型的基本用法就不多说了,主要是一个编译期的检查,也避免了我们代码中的强制转换,比较经典的用法有泛型D ...

  4. swift 有些语法还是不支持。

    <pre name="code" class="html">"func hasAnyMatches(list: Int[], condit ...

  5. 微软vs2015先行,Visual Studio 2015正式版离线iso及在线下载(附key)附带百度云盘地址

    win10正式版发布之前我们迎来了vs2015正式版,迫不及待要下载朋友可以看看 Visual Studio Community 2015简体中文版(社区版,针对个人免费): 在线安装exe:http ...

  6. 第五天学习内容 for循环,嵌套

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  7. perl 贪婪匹配小例子

    redis01:/root# cat x2.pl my $str="a19823a456123"; if ($str =~/a(.*)23/){print "1----& ...

  8. ResourceManager架构解析

    RM作为master管理着所有的集群资源,它会和NM和特定application的AM共同工作 1. NodeManagers NM从RM中获得指令,并管理着单节点上可用资源 2. Applicati ...

  9. Hacker News网站的文章排名算法工作原理

    In this post I'll try to explain how Hacker News ranking algorithm works and how you can reuse it in ...

  10. SAP屏幕框架的创建

    1.创建包括文本的基本框架 REPORT ztest_sum. TABLES:mara,syst. WITH FRAME TITLE mytitle. "mytitle是框架上的文本 ) A ...