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

文章目录

    需求

    点击 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 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式