android复杂的列表视图新写法MultiType

MultiType 的特性
轻盈,整个类库只有 10 个类文件,aar 或 jar 包大小只有 10KB
周到,支持 局部类型池 和 全局类型池,并支持二者共用,当出现冲突时,以局部的为准
灵活,几乎所有的部件(类)都可被替换、可继承定制,面向接口/抽象编程
纯粹,只负责本分工作,专注多类型的列表视图 类型分发
高效,没有性能损失,内存友好,最大限度发挥 RecyclerView 的复用性
可读,代码清晰干净、设计精巧,极力避免复杂化,可读性很好,为拓展和自行解决问题提供了基础
MultiType 能轻松实现如下页面,它们将在示例篇章具体提供

注:MultiType 内部引用了 recyclerview-v7:24.2.1,如果你不想使用这个版本,可以使用 exclude 将它排除掉,再自行引入你选择的版本。示例如下:
使用 MultiTypeTemplates 插件自动生成代码
在基础用法中,我们了通过 3 个步骤完成 MultiType 的初次接入使用,实际上这个过程可以更加简化,MultiType 提供了 Android Studio 插件来自动生成代码:MultiTypeTemplates,源码也是开源的,https://github.com/drakeet/MultiTypeTemplates,不仅提供了一键生成 item 类文件和 ItemViewProvider,而且是一个很好的利用代码模版自动生成代码的示例。其中使用到了官方提供的代码模版 API,也用到了我自己发明的更灵活修改模版内容的方法,有兴趣做这方面插件的可以看看。
话说回来,安装和使用 MultiTypeTemplates 非常简单:
Step 1. 打开 Android Studio 的设置 -> Plugin -> Browse repositories,搜索 MultiTypeTemplates 即可获得下载安装:

Step 2. 安装完成后,重启 Android Studio. 右键点击你的 package,选择 New -> MultiType Item,然后输入你的 item 名字,它就会自动生成 item 模型类 和 ItemViewProvider 文件和代码。
比如你输入的是 “Category”,它就会自动生成 Category.java 和 CategoryViewProvider.java.
特别方便,相信你会很喜欢它。未来这个插件也将会支持自动生成布局文件,这是目前欠缺的,但不要紧,其实 AS 在这方面已经很方便了,对布局 R.layout.item_category 使用 alt + enter 快捷键即可自动生成布局文件。
使用 全局类型池
在基础用法中,我们并没有提到 全局类型池,实际上,MultiType 支持 局部类型池 和 全局类型池,并支持二者共用,当出现冲突时,以局部的为准。使用局部类型池就如上面的示例,调用 adapter.register() 即可。而使用全局类型池也是很容易的,MultiType 提供了一个内置的 GlobalMultiTypePool 作为全局类型池来存储类型和 view 关系,使用如下:
只要在使用你的全局类型之前任意位置注册类型,通过调用 GlobalMultiTypePool.register(…) 静态方法完成注册。推荐统一在 Application 初始便进行注册,这样代码便于寻找和阅读。
之后回到你的 Activity,调用 adapter.applyGlobalMultiTypePool() 方法应用你注册过的全局类型即可。
GlobalMultiTypePool 让一些普适性的类型能够全局共用,但使用全局类型池不当也会带来问题,这是没有全然采用全局类型池的原因。问题在于全局类型池是静态的,如果你在 Activity 中注册全局类型(虽然并不推荐。因为全局类型最好统一在一个地方注册,便于管理),并传入带 Activity 引用的变量进去,就可能造成内存泄露。举个例子,如下是一个很常见的场景,我们把一个点击回调传递给 provider,并注册到全局类型池:
public class LeakActivity extends Activity {
private MultiTypeAdapter adapter;
private Items items;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
items = new Items();
adapter = new MultiTypeAdapter(items);
OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
// …
}
}
/* 在 applyGlobalMultiTypePool 之前注册全局 */
GlobalMultiTypePool.register(Post.class, new PostViewProvider(listener));
adapter.applyGlobalMultiTypePool(); // listener -> LeakActivity.this 引用链,若没有及时释放,就会引起内存泄露。
因此,在使用全局类型池时,最好不要给 provider 传递回调对象或者外部引用,否则就应手动释放或使用弱引用(WeakReference)。除此之外,全局类型池没有什么其他问题,类型池都只会持有 class 和非常轻薄的 provider 对象。我做过一个试验,就算拥有上万个类型和 provider,内存占用也是很少的,索引速度也很快,在主线程连续注册一万个类型花费不过 10 毫秒的时间,何况一般一个应用根本不可能有这么多类型,完全不必担心这方面的问题。
另外一个特性是,不管是全局类型池还是局部类型池,都支持重复注册类型。当发现重复时,之后注册的会把之前注册的类型覆盖掉,因此对于全局类型池,需要谨慎进行重复注册,以免影响到其他地方。
一个类型对应多个 ViewProvider
注:本文所有的 ViewProvider 都指的是 ItemViewProvider.
MultiType 天然支持一个类型对应多个 ViewProvider,但仅限于在不同的列表中。比如你在 adapter1 中注册了 Post.class 对应 SinglePostViewProvider,在另一个 adapter2 中注册了 Post.class 对应 PostDetailViewProvider,这便是一对多的场景。只要是在不同的局部类型池中,无论如何都不会相互干扰,都是允许的。
而对于在 同一个列表中 一对多的问题,首先这种场景非常少见,再者不管支不支持一对多,开发者都要去判断哪个时候运用哪个 ViewProvider,这是逃不掉的,否则程序就无所适从了。因此,MultiType 不去特别解决这个问题,如果要实现同一个列表中一对多,只要空继承你的类型,然后把它视为新的类型,注册到你的类型池中即可。
与 ViewProvider 通讯
ItemViewProvider 对象可以接受外部类型、回调函数,只要在使用之前,传递进去即可,例如:
OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
// …
}
}
adapter.register(Post.class, new PostViewProvider(xxx, listener));
但话说回来,对于点击事件,能不依赖 provider 外部内容的话,最好就在 provider 内部完成。provider 内部能够接收到 Views 和 数据,大部分情况下,完全有能力不依赖外部 独立完成逻辑。这样能使代码更加模块化,便于解耦,例如下面便是一个完全自包含的例子:
public class SquareViewProvider extends ItemViewProvider<Square, SquareViewProvider.ViewHolder> {
@NonNull @Override
protected ViewHolder onCreateViewHolder(
@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
View root = inflater.inflate(R.layout.item_square, parent, false);
return new ViewHolder(root);
}
@Override
protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Square square) {
holder.square = square;
holder.squareView.setText(valueOf(square.number));
holder.squareView.setSelected(square.isSelected);
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView squareView;
private Square square;
ViewHolder(final View itemView) {
super(itemView);
squareView = (TextView) itemView.findViewById(R.id.square);
itemView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
itemView.setSelected(square.isSelected = !square.isSelected);
}
});
}
}
}
使用断言,比传统 Adapter 更加易于调试
众所周知,如果一个传统的 RecyclerView Adapter 内部有异常导致崩溃,它的异常栈是不会指向到你的 Activity,这给我们开发调试过程中带来了麻烦。如果我们的 Adapter 是复用的,就不知道是哪一个页面崩溃。而对于 MultiTypeAdapter,我们显然要用于多个地方,而且可能出现开发者忘记注册类型等等问题。为了便于调试,开发期快速失败,MultiType 提供了很方便的断言 API: MultiTypeAsserts,使用方式如下:
import static me.drakeet.multitype.MultiTypeAsserts.assertAllRegistered;
import static me.drakeet.multitype.MultiTypeAsserts.assertHasTheSameAdapter;
public class SimpleActivity extends MenuBaseActivity {
private Items items;
private MultiTypeAdapter adapter;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
items = new Items();
adapter = new MultiTypeAdapter(items);
adapter.register(TextItem.class, new TextItemViewProvider());
for (int i = 0; i < 20; i++) {
items.add(new TextItem(valueOf(i)));
}
/* 断言所有使用的类型都已注册 */
assertAllRegistered(adapter, items);
recyclerView.setAdapter(adapter);
/* 断言 recyclerView 使用的是正确的 adapter */
assertHasTheSameAdapter(recyclerView, adapter);
}
}
assertAllRegistered 和 assertHasTheSameAdapter 都是可选择性使用,assertAllRegistered 需要在加载或更新数据之后, assertHasTheSameAdapter 必须在 recyclerView.setAdapter(adapter) 之后。

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=1578,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?