Jetpack-当 RecycleView 遇到 Databinding (二)
配合 Databinding Listadapter 实现极简 RecycleView (二)
资料来源:
https://developer.android.com/topic/libraries/data-binding
https://fraggjkee.medium.com/recyclerview-2020-a-modern-way-of-dealing-with-lists-in-android-using-databinding-part-2-df69f0a741f8更新
1
221.02.09 初始化
21.02.10 补充一个 recycleview 的坑
导语
最近重写 deepsleep 又回到这个主题了,参考 RecyclerView 2020: a modern way of dealing with lists in Android using DataBinding 又再次简化了写法.
目标:
- 重用 RecycleAdapter 和 ViewHolder,不需要每个类型都写一个.
- UI 的局部刷新,不需要每次都通知整个列表刷新.
- 与 databinding 写法结合,越简单越好.
方案
Jetpack-当 RecycleView 遇到 Databinding 有几个问题
- 每次都要给 RecycleAdapter layoutid,这样没法在同一个 RecycleView 创建多种布局.
- 事件处理也是类似的,外部传入 Handle,无法在不通布局上通用.
- RecycleAdapter 创建再绑定数据,到 Activity/Fragment 中实现还是繁琐.
因此参考 RecyclerView 2020: a modern way of dealing with lists in Android using DataBinding 有了几点调整.
- 在原有 item 结构上添加 Handle 和 layoutid,这样就能随意复用布局了.
- 传入 items 列表时,结合 databinding 简化了绑定数据.
Base
抽象出 BaseItemHandle 和 BaseItem,需要其他 item 继承自 BaseItem.
BaseItemHandle 是事件处理的基类.
1 | open class BaseItemHandle { |
BaseItem 是所有传入 Adapter 的 Item 的基类,创建时需要指定 handle 和 layoutId.同时配合使用 ListAdapter 需要实现对比 Item 的两个方法.
1 | open class BaseItem( |
DiffCallback 是 ListAdapter 对比的实现,调用 BaseItem 的两个方法.
1 | class DiffCallback : DiffUtil.ItemCallback<BaseItem>() { |
Adapter
ViewHolder 的具体作用仅仅是绑定 databinding 生成类和数据了.(要求xml 布局中变量名是 item
)
1 | class ViewHolder(var binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) { |
RecycleAdapter
- 必须实现的是
onCreateViewHolder
和onBindViewHolder
. onCreateViewHolder
创建 ViewHolder.- 需要 databinding 生成类 调用
DataBindingUtil.inflate
需要 layoutid. - 这里覆写
getItemViewType
是其返回的值为 layoutid.
- 需要 databinding 生成类 调用
onBindViewHolder
是绑定,这里仅仅是调用ViewHolder::bind
- 很重要的一点必须覆写
getItemId
,否则会有条目重复. 原因大概是 recycleview 的缓存机制. - 如果每个 item 都有唯一的 id 则设置
setHasStableIds(true)
会提高性能.
1 | class RecycleAdapter : ListAdapter<BaseItem, ViewHolder>(DiffCallback()) { |
xml
在 item 的 xml 布局中必须将变量名称声明成 item.
1 | <data> |
事件处理,则调用 handle.
1 | <ImageView |
绑定
声明一个 databinding 自定义方法
1 |
|
在这个方法中完成了 adapter 的绑定.而且全局复用一个 adapter.
之后要在父布局中声明带 list 数据的变量
1 | <data> |
声明 RecyclerView
1 | <androidx.recyclerview.widget.RecyclerView |
之后要在 acticity/fragment 中初始化 AppViewModel,绑定数据.这里用的是 livedata 还需要绑定声明周期.
1 | var binding = FragmentAppBinding.inflate(inflater, container, false) |
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position -1(offset:0).st
上面的一切完美,但是快速滑动获取快速切换数据源时,会出现闪退.logcat 提示 java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position -1(offset:0).st
搜索查阅后,只能说这是个 recycleview 的内部 bug,似乎是快速刷新时数据源没有来的及更新,recycleview 就刷新了.adapter 数据和集合中数据不一致.
解决方案
- 不再使用 ListAdapter 自行实现 Adapter 的各种 notifyxx()
- 覆写 LayoutManager,将这个错误打印而不是抛出.
因为实际上数据源刷新后,recycleview 能够正常显示,因此这个错误直接捕获不处理也是可行的.
MyLayoutManager
继承自 LinearLayoutManager.在 onLayoutChildren
中捕获错误输出.
1 | class MyLayoutManager : LinearLayoutManager { |
将 recycleview 的 layoutmamger 替换
1 | app:layoutManager=".ui.databinding.MyLayoutManager" |
一切 ok
结语
目前这个方案就在 deepsleep 中使用.只剩下 recycleview 的多选批处理了,来回折腾了好几次,都没有太好的办法.因此暂时搁置.