如何通过Databinding的观察者模式自动刷新RecyclerView.Adapter
最近项目中用 Android 的 Databinding 用的风生水起,自己通过三个项目,总结出了一套自用的快速开发模板,基本套路是一个页面由一个 Activity/Fragment + ViewModel 组成,力求做到 Activity/Fragment 只负责 UI 的渲染,所有的数据逻辑操作都在对应的 ViewModel 里完成,ViewModel 只会持有一个 ApplicationContext 引用,需要用到 Activity Context 的操作,通过接口去宿主 Activity/Fragment 里实现。模板 github 链接:zlibrary
到这里可能有人不明白了,ViewModel 里没有 Activity Context 或者 View 的引用, 那每更新一种数据岂不是都要写个接口?那不是很麻烦么。诶~这就是 Databinding 的魅力所在了,可以让通过将数据源包装成一个被观察者,在宿主 Activity/Fragment 或者对应的 xml 文件中来响应数据的变化。Databinding 的基本用法就不多啰嗦了,直接看 Google 的官方文档即可,文档不长,也比较简单:Data Binding Library
使用说明
本文主要介绍介绍如何通过观察 ViewModel 里的 List 变化,来使 Adapter 自发的响应这个变化来刷新列表。
首先在 ViewModel 里创建一个包装成被观察者的 List 数据源:
public ObservableArrayList<Entity> mList = new ObservableArrayList<>();
可以看到,创建方法和创建普通的 List 是一样的,看一下ObservableArrayList
的源码,会发现它是ArrayList
的子类,并且实现了ObservableList
接口,而ObservableList
接口又是List
接口的子接口,看到这就很清楚了,基本上可以把ObservableArrayList
和
当成普通的
ObservableListArrayList
和List
来使用,只不过前两者实现了一个观察者的功能。
首先将 Adapter 的数据源设置为该 List,然后在宿主 Activity/Fragment 里设置这个数据源的回调接口,在对应的回调方法里调用 Adapter 对应的 notify 方法.。
完整代码如下:
// 创建 Adapter
mAdapter = new HistoryOrCollectAdapter(getViewModel().mList);
// 设置 List 源变动的监听器,在对应的回调中调用 adapter 的更新方法
getViewModel().mList.addOnListChangedCallback(new ObservableList.OnListChangedCallback() {
@Override
public void onChanged(ObservableList observableList) {
adapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeChanged(i, i1);
}
@Override
public void onItemRangeInserted(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeInserted(i, i1);
}
@Override
public void onItemRangeMoved(ObservableList observableList, int i, int i1, int i2) {
if (i2 == 1) {
adapter.notifyItemMoved(i, i1);
} else {
adapter.notifyDataSetChanged();
}
}
@Override
public void onItemRangeRemoved(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeRemoved(i, i1);
}
});
经过这样一番操作后,在 ViewModel 里就可以直接通过对 List 的数据结构进行修改,从而改变列表数据,而不用再手动调用Adapter.notifyDataSetChanged()
方法了。
更进一步
在实际开发中,一个 APP 往往会有许多个页面,许多个列表,要是都这样在 Activity/Fragment 里这样写一遍,不但工作量大,浪费时间,而且也不美观,那么完全可以将获取回调接口的步骤写成一个单独的静态方法,比如建立这样一个类:
public class ListFactory {
public static ObservableList.OnListChangedCallback getListChangedCallback(RecyclerView.Adapter adapter) {
return new ObservableList.OnListChangedCallback() {
@Override
public void onChanged(ObservableList observableList) {
adapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeChanged(i, i1);
}
@Override
public void onItemRangeInserted(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeInserted(i, i1);
}
@Override
public void onItemRangeMoved(ObservableList observableList, int i, int i1, int i2) {
if (i2 == 1) {
adapter.notifyItemMoved(i, i1);
} else {
adapter.notifyDataSetChanged();
}
}
@Override
public void onItemRangeRemoved(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeRemoved(i, i1);
}
};
}
}
这样,就可以传入对应的 Adapter 实例,获取对应数据源的修改回调接口了,然后 Activity/Fragment 里面的代码就可以写成这样:
private ObservableList.OnListChangedCallback mListChangedCallback;
private void initAdapter() {
mAdapter = new HistoryOrCollectAdapter(R.layout.item_history_collect, getViewModel().mList);
getViewModel().mList.addOnListChangedCallback(ListFactory.getListChangedCallback(mAdapter));
}
注意
有的读者可能注意到了,我在监听回调里的onItemRangeMoved()
方法里写的是这么一段:
@Override
public void onItemRangeMoved(ObservableList observableList, int i, int i1, int i2) {
if (i2 == 1) {
adapter.notifyItemMoved(i, i1);
} else {
adapter.notifyDataSetChanged();
}
}
这个回调是在 List 里的连续的元素整个移动的情况下会进行的回调,然而 RecyclerView 的 Adapter 里并没有对应的方法,只有单个元素移动时的方法,所以需要在回调方法中做一个判断,如果移动的元素只有一个,就调用 Adapter 对应的方法,如果超过一个,就直接调用notifyDataSetChanged()
方法即可。
顺便说一下,在实际项目中,一般一次只会移动一个条目的位置,目前我还没见过一次移动一串条目的位置的使用呢。
还有一点,List 的 removeAll()
方法是没有对应的回调方法的,也就是说调用 List 的removeAll()
方法批量删除一些元素,是不会自动反应在 Adapter 上的。通过仔细观察就能发现,ObservableList 和 Adapter 的 notify 相关的方法,都是对连续的元素生效,像removeAll()
这种,其参数 List 的元素在数据源 List 里面有可能是分散的,所以不会回调OnListChangedCallback
接口里的任何方法。但是我认为,其实可以回调该接口里的onChanged()
方法,直接通知 Adapter 整体刷新就好。
所以我现在对removeAll()
方法的处理,就是用 for 循环挨个元素remove
:
for (Entity entity: mCheckedList) {
mList.remove(entity);
}
这样做虽然有点丑,但还是能接受的,总比另外写个接口去宿主 Acitivity/Fragment 里调用Adapter.notifyDateSetChanged()
要好看,哈哈。
上一篇: WCF中常用的binding方式
下一篇: 将数据库中的数字显示为文字