Jetpack-当 RecycleView 遇到 Databinding
配合 Databinding Listadapter 实现极简 RecycleView
资料来源:
https://developer.android.com/topic/libraries/data-binding
更新
1
20.04.17 初始化
导语
- 两年前,写 MyPrivacy 的时候,就遇到过 recycleview 需要配合 databinding 的情况,当但是实现的那个乱啊…
- 刷 Jetpack 官方例程,加上之前的了解,试图可以比较优雅的实现 recycleview .
- 目标:
- 重用 RecycleAdapter 和 ViewHolder,不需要每个类型都写一个.
- UI 的局部刷新,不需要每次都通知整个列表刷新.
- 便捷的事件处理,越简单越好.
绑定数据
整体的思路依旧是从 借助 android databinding 框架,逃离 adapter 和 viewholder 的噩梦 (1) 而来再改进.
回想一下 RecycleAdapter 和 ViewHolder 都干了那些工作?
- Adapter 把数据集设置到整个 item 的List.
- ViewHolder 则是数据到具体 item 的设置细节.
那么 Databinding 呢?
- 启用数据绑定后,每一个数据绑定的 xml 布局会生成一个绑定类.
- 之后我们需要将对应的数据,设置到绑定类,之后的显示由绑定类自行完成.
以往因为每一个 item 的布局都不一样,每个不同的 item 都要写一个 ViewHolder,在代码中完成数据到 UI . databinding 数据到 UI 的过程集成在xml中,代码中只剩下一个绑定操作,这样思路就出来了:
- 使用 databinding ,使 ViewHolder 只剩下一个绑定数据的过程.数据到 UI 的过程都在 xml 布局中声明.
- 这样就使不同的 item 共用同一个 ViewHolder 成为可能.
- 问题: 不同的 xml 生成的绑定类都不同,如何把绑定数据这个操作统一?
翻阅官方文档,我们可以通过 DataBindingUtil 类,传入 xml 布局直接生成绑定类,在 ViewHolder 中传入 ViewDataBinding 这个绑定类的基类完成绑定,只有一点副作用,需要所有 xml 中的变量名相同,这样通过才能正确通过绑定类的基类实现数据绑定.
布局
1
2
3
4
5<data>
<variable
name="item"
type="com.js.nowakelock.data.db.entity.AppInfo" />
</data>绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14class ViewHolder(var binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: BaseItem?) {
binding.setVariable(BR.item, item)
binding.executePendingBindings()
}
}
//创建 ViewHolder 实例时,生成绑定类.
ViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
layout, parent, false
), handler
)
这样 ViewHolder 就可以在不同的布局间通用了,但是要求我们必须传入 item 的 xml 布局.这里有两种方式.
- 在 item 对应的类中增加 getLayout 方法,覆写 Adapter 的 getItemViewType 方法返回 item.getLayout.(原教程就是这么干的)
- 因为我这里都是完全相同的 item,所以直接在 Adapter 传入了布局 xml.
之后要处理 RecycleAdapter 了,这里有点搞笑,本来要实现 RecycleView 的局部刷新,需要写 N 多的方法,都快写完了…遇到了 Listadapter.
Listadapter 是
com.android.support:recyclerview-v7:27.1.0
引入的新方法,局部刷新这些事情 Listadapter 全部干了…只需要给 Listadapter 传入 DiffUtil.ItemCallback 实现类,实现比较两个 item 是否相同,和同一个 item 是否有更新的方法…具体用法使用更少代码的ListAdapter这里我们抽象出一个接口 BaseItem,作为所有 Item 的基类.
1
2
3
4
5interface BaseItem {
fun getID(): String = ""//两个item 是不是一个 item
fun getContent(): Int = 0 //同一个 item 内容是否有更新
// fun getLayout(): Int = 0 //如果需要传入布局
}实现 DiffUtil.ItemCallback 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class DiffCallback : DiffUtil.ItemCallback<BaseItem>() {
/*是否是同一对象*/
override fun areItemsTheSame(
oldItem: BaseItem,
newItem: BaseItem
): Boolean {
return oldItem.getID() == newItem.getID()
}
/*内容是否相同*/
override fun areContentsTheSame(
oldItem: BaseItem,
newItem: BaseItem
): Boolean {
return oldItem.getContent() == newItem.getContent()
}
}之后在 RecycleAdapter 声明 Listadapter 时传入 DiffCallback.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class RecycleAdapter(private val layout: Int) :
ListAdapter<BaseItem, ViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
layout, parent, false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
}上面就是 RecycleAdapter 的全部代码,简洁的不行…
在 Fragment/Activiety 创建 adapter 的实例,并
adapter.submitList(items)
设置数据集,这样基础的绑定数据就做完了.
事件处理
事件处理我更倾向于在 item 这个层级处理,而不是都扔到上层.
事件处理 databinding 给了两种选择,方法引用 和 监听器绑定.其中监听器绑定更加灵活,所以这里选择监听器绑定.
以最简单的点击事件为例
首先需要声明一个处理事件的处理类,如果是像 NoWakeLock 比较简单的布局,可以通过 Adapter 传入,如果布局比较复杂,可以像上文处理 Layout 一样,在 BaseItem 中增加一个 getHandler 的方法.
与数据一样,为了能够正确绑定,需要将不同布局的事件处理方法变量名设为相同的,这里是 handler,绑定时就是
binding.setVariable(BR.handler, XXX)
.1
2
3
4
5
6
7
8
9
10
11
12
13<data>
<variable
name="item"
type="com.js.nowakelock.data.db.entity.AppInfo" />
<variable
name="handler"
type="com.js.nowakelock.ui.appList.Handler" />
</data>
<xxxView ...
android:onClick="@{(theView)->handler.onClick(theView,item)}"
...
>监听器绑定可以给具体的方法,传入 View 对象.这里与 BaseItem 一样,声明一个处理事件的基类 BaseHandler.然后我在 Handler 中增加了一个 ViewModel 对象,这样可以直接借助 ViewModel 处理对应数据.如果 Handler 是集成在 BaseItem 中的需要用到 ViewModel,可以在 items 传入 Adapter 之前处理一下 items.或者借助依赖注入,注入全局单例的 ViewModel.
1
2
3
4
5
6
7
8
9
10
11
12open class BaseHandler {
private val TAG = "BaseHandler"
open fun Click() {
LogUtil.d(TAG, "click")
}
}
class Handler(private val viewModel: AppListViewModel) : BaseHandler() {
fun onClick(view: View, appInfo: AppInfo) {()
//do something
}
}
最后 Adapter 和 ViewHolder 的代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class ViewHolder(var binding: ViewDataBinding, private val handler: BaseHandler) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: BaseItem?) {
binding.setVariable(BR.item, item)
binding.setVariable(BR.handler, handler)
binding.executePendingBindings()
}
}
class RecycleAdapter(private val layout: Int, private val handler: BaseHandler) :
ListAdapter<BaseItem, ViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
layout, parent, false
), handler
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
结语
- 这一篇还是略显凌乱,该写的都写了,该说的都说了,就是读完,不能酣畅淋漓的一次性读完…功力还不够吧…
- 借助 Jetpack 开发完初版的 NoWakeLock,总的感受就是,Jetpack 几乎处理了日常应用的全部,真方便.kotlin 真好用且简结,没有废话.(代价是必须写过代码才算是会用了)