欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何通过Databinding的观察者模式自动刷新RecyclerView.Adapter

程序员文章站 2022-06-09 17:55:56
...

最近项目中用 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
ObservableList
当成普通的ArrayListList来使用,只不过前两者实现了一个观察者的功能。

首先将 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()要好看,哈哈。

相关标签: android binding