RecyclerView 子项 View Holder 的点击事件响应

更新日期: 2021-02-17 阅读次数: 3107 字数: 501 分类: Android

需求

点击 RecyclerView 中的一个 checkbox 子项,更新本地 sqlite 数据库中的一行数据的选中状态字段。

方案对比:

方案一 (不可行):使用 Data Binding 事件处理机制中的 listener binding。

参考

https://developer.android.com/topic/libraries/data-binding/expressions#listener_bindings

采用这个方案,就能把事件响应方法放在 Fragment 中,也就可以使用 Fragment 中的 View Model 了,进而进行 Room Update 操作。

唯一不确定的就是 Fragment 能否作为变量放入 Layout 中,而且每个子项的 layout 都引入一遍 fragment 会不会有内存问题。

愚蠢如我,在 codelabs 中发现了正确的做法

https://developer.android.com/codelabs/android-databinding#5

即,layout 中直接引入 View Model:

<data>
    <variable name="viewmodel" type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
</data>

但是在 adapter 中如何赋值呢?

尝试在 adapter 中获取 view model:

https://stackoverflow.com/questions/44495323/how-to-use-architecture-components-viewmodel-inside-recyclerview-adapter

https://stackoverflow.com/questions/61364874/view-models-for-recyclerview-items

adapter 与 fragment 的数据交互

https://stackoverflow.com/questions/56336566/how-to-send-data-from-adapter-to-viewmodel-to-fragment

很多文章里说给 adapter 传递 view model 并不是推荐的做法。所以放弃。

方案二 (未测试):传递函数给 adapter

参考

https://stackoverflow.com/questions/65504527/update-status-from-viewholder-on-click-android-kotlin

https://www.avinsharma.com/android-basics-recyclerview-II/

方案三 (可行):使用 interface 实现

在 RecyclerView Adapter 的 View Holder 中设置响应事件处理方法。

一个非常详细的说明

https://stackoverflow.com/questions/63266686/how-do-i-remove-a-row-from-recyclerview-using-room

最终实现方案

最终参照第三个方案,采用 interface 的实现。这个方案虽然有点复杂,但是思路清晰,我觉得不错。

实现步骤:

1、adapter 中添加一个 interface,内置一个响应点击的函数

private lateinit var listener: ItemListener

override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
	val todo = getItem(position)
	holder.bind(todo)
	holder.binding.checkBox.setOnCheckedChangeListener {_, isChecked ->
		listener.onItemClicked(todo, isChecked)
	}
}

interface ItemListener {
	fun onItemClicked(item: Todo, isChecked: Boolean)
}

fun setListener(listener: ItemListener) {
	this.listener = listener
}

2、fragment 中实现这个 interface

class TodosFragment : Fragment(), TodoAdapter.ItemListener {
    ...
    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        ...
        adapter.setListener(this)
        ...
    }
    
    override fun onItemClicked(item: Todo, isChecked: Boolean) {
        item.done = if (isChecked) 1 else 0
        viewModel.updateTodo(item)
    }
}

data binding 中 listener binding 无响应的原因

android:onClick="@{(theView) -> fragment.completeChanged(todo)}"
android:onCheckedChanged="@{(cb, isChecked) -> fragment.completeChanged(todo, isChecked)}"

点击无响应。原因是没有对 layout 中的变量进行赋值 。。。

// checked handler for recycler view item
binding.viewmodel = viewModel

kotlin interface 介绍

https://oozou.com/blog/interface-in-kotlin-and-when-to-use-it-10

关于作者 🌱

我是来自山东烟台的一名开发者,有敢兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式