Vue 里实现一个列表
so easy,Vue 为我们提供了v-for
指令,可以绑定数组的数据来渲染一个项目列表:
1 | <div id="app-4"> |
1 | var app4 = new Vue({ |
数据和视图的绑定就完成了。
加载更多功能
只需要 todos.push(items)
,你就会发现列表末尾添加了新的项目。
下拉刷新功能
只需要 todos = items
,刷新功能就完成了
在 Vue 里更新 UI,似乎只需要对数据进行操作就可以了,UI 就会自动更新,这不就是传说中响应式编程嘛?
NOTE: 问题来了,如果 Android 也想响应式,只对数据进行操作,不去触碰
adapter.notifyItemXXX
系列api
就完成 RecyclerView 的更新吗?
当然是可以的咯,兄 dei!
Android 的 MVVM
注意,我并不会用 databinding 来实现。即使用了,你也会发现,似乎实现起来有困难。
前提情要
DiffUtil&LiveData
DiffUtil
DiffUtil,为我们提供了计算差异值的能力:
简单理解,DiffUtil
根据 cur&next
,就能计算出差异值,也就是 DiffUtil.DiffResult
。
LiveData
LiveData,我们可以先把它看成 RxJava
里的 PublishSubject
,只不过 LiveData
具有生命周期感知能力。
MVVM 的划分
这个模式我就不介绍了,也属于懒得介绍系列。这里讲下,MVVM 具体对应 Android 里的哪些东西。
- V=> Activity/Fragment,对标视图层,没啥可讲的
- M=>XXXRepo,对标数据层,就是获取内存数据/网络数据/本地数据的数据仓库
- VM=>XXXVM,这个是 MVVM 的核心,链接着 V 和 M。但是和 MVP 不同,是 V 持有 VM,而不是 VM 持有 V,V 订阅 VM 里的数据(LiveData 包裹的对象),当
LiveData
调用postValue/setValue
,V 就会收到数据变化通知
如图:
开始实现了
VM 层做的事情
数据域
对标 Vue 里的 data ,不过,我们这里需要把真正的数据用 LiveData 包裹起来,像这样:
1 | // 列表数据项 |
方法域
对标 Vue 里的 methods,定义一些业务方法,这里只有一个 loadData
方法,之后会详解。
计算属性
对标 Vue 里的 computed ,这里,我们将数据域暴露出去,方便 V 层的订阅/ 绑定:
1 | fun getUIStateModel() = _uiStateModel |
V 层做的事情
step1. 创建并获取 VM 的实例
1 | // create VM |
step2. 初始化组件,并设置需要的回调
1 | // init Recyclerview |
step3. 订阅/观察 VM 里的计算属性
1 | private fun observeVMState(vm: JuVM?) { |
step4. 在合适的生命周期方法中,调用 VM 里的方法
1 | override fun onCreate(savedInstanceState: Bundle?) { |
恭喜恭喜,基本完工
到这一步,我们算完成了一个页面的基本功能了。
数据和视图的绑定是如何实现的
首先得聊聊旧石器时代的事,那时候的我们是这么实现加载更多和下拉刷新的:
1 | // 下拉刷新 |
有什么问题呢?首先,刷新的代码太暴力了,直接 notifyDataSetChanged
导致所有 item 重新绑定一遍。接着,是加载更多,调用了 notifyItemRangeInserted
,需要传入起始位置以及新增项目的个数,如果参数错误,凉凉╮(╯▽╰)╭
这简直没法和 web 世界的 mvvm 框架比嘛,太 low 了有木有!
新石器时代
我想,屏蔽 adapter.notifyXXX
一系列 api
,只通过对数据项的操作就实现 RecyclerView 的更新。
是时候贴出 loadData
的代码了:
1 | // 加载数据 |
正如同文章开头说的,通过 Google
提供的 DiffUtil
,我们将对数据的操作封装为 DiffUtil.DiffResult,而且是在工作线程做这些事,接着切换线程,在主线程发布 DiffUtil.DiffResult
。最后,V 层就会收到订阅通知,至此,列表数据项与 RecyclerView
实现了绑定。
1 | vm?.let { juVM -> |
最后
我们实现了加载更多和下拉刷新功能,真实项目中,我们可能还会涉及到 RecyclerView 修改 item。当然,理解了上面论述的原理之后,实现这样的需求也不是太难。这篇文章把关键代码都列出来了,部分细节不明白的话,可以参考 github 上的完整实现