项目地址: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。获取不同的布局进行设置。

贴张效果图:

最新文章

  1. linux shell:nginx日志切割脚本
  2. [转载]win32 计时器使用
  3. log4net 发布到生产环境不写日志的解决方法--使用 NLog日志
  4. 查询条件Where
  5. 【bzoj1023】仙人掌图
  6. maven插件:tomcat插件和jetty插件的区别
  7. hive中简单介绍分区表
  8. mplayer windows configure修改
  9. AIR lame参数配置
  10. win7下简单FTP服务器搭建
  11. ASP.NET MVC- VIEW Using the TagBuilder Class to Build HTML Helpers Part 3
  12. TypeUtils -- Object 转为 强类型
  13. 服务器证书安装配置指南(SLB)
  14. Linux 三剑客(Awk、Sed、Grep)
  15. Java线程池源码解析
  16. C++设计模式——组合模式
  17. mysql 5.7.21 解压版安装配置方法图文教程
  18. 解决Maven web 项目 Cannot detect Web Project version. Please specify version of Web Project through ... 的错误
  19. 读《阿里Java开发手册》总结(1)
  20. ActiveMQ 的连接和会话

热门文章

  1. Hough变换在opencv中的应用
  2. Python学习笔记 — 函数
  3. Android学习之一:Cygwin简介
  4. Spring MVC 多选框 绑定 Entity 中的 list 属性
  5. JENKINS 打包发布脚本
  6. Linux编译多个不同目录下的文件以及静态库、动态库的使用
  7. Microsoft office PPT 2007 保存时速度慢(整理自网上)
  8. LeetCode77:Combinations
  9. asp.net 检查文件夹和文件是否存在
  10. jvm常用参数设置 good