【问题标题】:RecyclerView onClickListener with DiffUtilRecyclerView onClickListener 与 DiffUtil
【发布时间】:2023-03-13 09:05:01
【问题描述】:

如何使用 DiffUtil 回调处理 RecyclerView 的 onClick?以及如何更改 recyclerview 中所选项目的背景颜色?我在一个活动中有两个 RecyclerViews。当用户点击 RecyclerView A 中的项目时,RecyclerView B 中会发生一些事情。

这是课

import androidx.room.ColumnInfo

data class SkladTuple(
    @ColumnInfo(name = "sklad") val sklad: Int?,
    @ColumnInfo(name = "reg") val reg: Int?
)

这是适配器:

class SkladAdapter: ListAdapter<SkladTuple, SkladAdapter.PolozkaViewHolder>(DiffCallback())
{
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PolozkaViewHolder {
        val binding = SkladyItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return PolozkaViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PolozkaViewHolder, position: Int) {
        val currentItem = getItem(position)
        holder.bind(currentItem)  

    }
    class PolozkaViewHolder(private val binding: SkladyItemBinding): RecyclerView.ViewHolder(binding.root){
        fun bind(polozkaSklad: SkladTuple){
            binding.apply {
                tvSklad.text = polozkaSklad.sklad.toString()
                tvRegal.text = polozkaSklad.reg.toString()

            }
        }
    }
    class DiffCallback: DiffUtil.ItemCallback<SkladTuple>(){
        override fun areItemsTheSame(oldItem: SkladTuple, newItem: SkladTuple) =
            oldItem.sklad == newItem.sklad

        override fun areContentsTheSame(oldItem: SkladTuple, newItem: SkladTuple) =
            oldItem == newItem
    }
}

这是活动

@AndroidEntryPoint
class DokladActivity : AppCompatActivity() {
    private val skladViewModel: SkladViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityDokladBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.btVybratDoklad.setOnClickListener{
            openActivity(binding.root)
        }
        val skladAdapter = SkladAdapter()
        val dokladAdapter = DokladAdapter()
        binding.apply {
            recyclerViewSklady.apply {
                adapter = skladAdapter

                layoutManager = LinearLayoutManager(this@DokladActivity)
            }
            skladViewModel.skladyPolozky.observe(this@DokladActivity) {
                skladAdapter.submitList(it)
                Log.d("Doklad", skladAdapter.currentList.toString())
            }

            recyclerViewDoklady.apply {
                adapter = dokladAdapter
                layoutManager = LinearLayoutManager(this@DokladActivity)
            }
            skladViewModel.dokladyPolozky.observe(this@DokladActivity){
                dokladAdapter.submitList(it)
                Log.d("Doklad", dokladAdapter.currentList.toString())
            }
        }


    }

    fun openActivity(view: View){
        val intent = Intent(this,PolozkaActivity::class.java )
        startActivity(intent)
    }
}


这是视图模型

@HiltViewModel
class SkladViewModel @Inject constructor(
    repository: SybaseRepository
): ViewModel(){
    val skladyPolozky = repository.getAllSkladFromPolozka().asLiveData()
    val dokladyPolozky = repository.getAllHlavickyToDoklad().asLiveData()
}

这是布局 - 我有两个回收站视图。当用户点击 recyclerview A 中的项目时,recyclerview B 中会发生一些事情

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.02" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.7" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.25" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline11"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.75" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline12"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.9" />

    <TableLayout
        android:stretchColumns="1,2"
        android:layout_margin="8dp"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline10"
        app:layout_constraintEnd_toStartOf="@+id/guideline9"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline8">

        <TableRow>
            <TextView
                android:text="SKLAD"
                android:textSize="16dp"
                android:textStyle="bold"
                android:padding="10dp"
                android:layout_gravity="center"
                android:layout_column="1"/>
            <TextView
                android:text="REGAL"
                android:textSize="16dp"
                android:textStyle="bold"
                android:padding="10dp"
                android:layout_gravity="center"
                android:layout_column="1"/>
        </TableRow>

        <androidx.recyclerview.widget.RecyclerView
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/recycler_view_sklady"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            tools:listitem="@layout/sklady_item"
            />

    </TableLayout>

<TableLayout
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:layout_marginEnd="16dp"
    android:layout_marginBottom="8dp"
    android:stretchColumns="1,2,3,4,5"
    app:layout_constraintBottom_toTopOf="@+id/guideline11"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@+id/guideline10"
    >
    <TableRow>
        <TextView
            android:layout_column="1"
            android:layout_gravity="center"
            android:padding="10dp"
            android:text="U"
            android:textSize="16dp"
            android:textStyle="bold" />
        <TextView
            android:layout_column="1"
            android:layout_gravity="center"
            android:padding="10dp"
            android:text="DOKL"
            android:textSize="16dp"
            android:textStyle="bold" />
        <TextView
            android:layout_column="1"
            android:layout_gravity="center"
            android:padding="10dp"
            android:text="ODB"
            android:textSize="16dp"
            android:textStyle="bold" />
        <TextView
            android:layout_column="1"
            android:layout_gravity="center"
            android:padding="10dp"
            android:text="ORG"
            android:textSize="16dp"
            android:textStyle="bold" />
        <TextView
            android:layout_column="1"
            android:layout_gravity="center"
            android:padding="10dp"
            android:text="DATUM"
            android:textSize="16dp"
            android:textStyle="bold" />

    </TableRow>


    <androidx.recyclerview.widget.RecyclerView
        xmlns:app="http://schemas.android.com/apk/res-auto"

        android:id="@+id/recycler_view_doklady"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:listitem="@layout/doklady_item"/>
</TableLayout>

    <Button
        android:id="@+id/bt_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:text="Zpět"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline12" />

    <Button
        android:id="@+id/bt_vybrat_doklad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="24dp"
        android:text="Vybrat doklad"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline12" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="209dp"
        android:layout_height="50dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintBottom_toTopOf="@+id/guideline12"
        app:layout_constraintEnd_toStartOf="@+id/guideline9"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline11" />
</androidx.constraintlayout.widget.ConstraintLayout>

【问题讨论】:

    标签: android kotlin android-recyclerview android-adapter


    【解决方案1】:

    怎么可能……

    您有一个 ViewModel,它通过您的 RecyclerViews 需要的列表(两个适配器,两个列表)公开了两个可观察的 liveData。

    判断 List A, Item 1 被点击,因此 List B, Item N 必须改变其背景的逻辑绝对是 Recycler View, Adapter, Activity 的范围之外,甚至可能是 ViewModel。

    最终,当数据突变发生时(您点击第 1 项),您会收到此事件,对其采取行动(又名:将其发送到 Viewmodel -> 存储库 -> 转换数据 ->发布包含变异数据的新列表。

    最终,您的 ViewModel 将从存储库接收新数据,并更新您的活动正在观察的 LiveData,这将导致适配器提交新数据,您的 DiffUtils 将获取这些数据,从而导致适配器重新绑定修改后的视图。

    更新

    根据您链接的问题/答案以及您的评论,我认为这值得进一步评论。

    1. 该人说:“处理适配器上的点击,也不使用接口不是一个好主意”,然后继续“处理”点击,并且使用匿名函数(所以它本来可以是一个用于测试目的的接口,但它却把它变成了一个“高阶函数”。

    2. 这和我描述的原理完全一样,只是界面更容易理解。

    想想这样的责任:

    ViewHolder:它对数据、回收器视图、适配器等一无所知。但它可以直接访问列表的每个“行/列”。它拥有视图,它可以根据它提供的数据来操作它们,并被告知在所需视图旁边“保留”。它不会创建视图(它被告知),但它拥有它们。关于点击,所有这一切都可以做,要么公开一个视图(这样一个点击监听器可以由外部的人设置)或简单地委托它。

    Adapter:除了视图的类型(如果使用)以及如何创建 viewHolders 和“绑定”它们之外,它对视图一无所知。所以它介于两者之间,但它确实可以访问提供给它的数据源(您提供的列表),并且它确实可以访问它创建的每个“视图持有者”;它或多或少知道屏幕上的内容,并且可以将位置映射到数据。

    RecyclerView/Fragment/ViewModel:它们不知道适配器内部会发生什么(也不应该知道),但它们主要对点击(或事件)何时发生感兴趣,因此他们大概可以对此做些什么。

    基于这一切,让我们看看this person's solution(与我的建议相比):

    1. Adapter的构造函数被修改为接收一个函数:

    (private val onSelect: (YourDataType?) -&gt; Unit)

    我通常定义一个接口:

    interface SomethingClickListener {
      fun onSomethingICaredForHappened(something: Something)
    }
    

    并将它传递给我的适配器,但想法是一样的;我更喜欢这个接口,因为它更容易将依赖注入与具体类型一起使用(并且更容易测试),但我相信这只是个人偏好。

    class YourAdapter(private val clickListener: SomethingClickListener)

    1. 适配器内部发生的事情是相同的原理。您需要在您希望被点击的视图上设置实际的点击监听器(如果您希望整个“行”可点击,则需要设置整个 itemView)。

    通过简单地使用单击侦听器的匿名实现并捕获发送到绑定方法的值(这很好),您可以(或想要在其中做更多的事情)来获得链接的答案。所以你可以使用另一个界面或者这样做,本质上,这个想法是一样的,你传递点击事件

    binding.root.setOnClickListener {
        onSelect(yourDataType)
      }
    

    所以它将dataType 直接传递给onSelect(这是提供给适配器的函数)。在这个阶段,唯一的逻辑是附加与被点击的 viewBinding 关联的“数据”,这是适配器/视图持有者可以并且应该提供的“唯一”信息。

    onSelect 之后会发生什么,你的片段/活动收到了这个,然后你应该调用你的 ViewModel 说:

    viewModel.thisThingWasClicked(theThingYouReceived)

    你明白了……

    我已经有一段时间没有更新它了(我想我是在 3 年前用 Java 写的,然后我天真地将它转换为 Kt)除了更新库并确保它仍然可以编译,但你可以看到一个“两个接口方法”(有点老派,现在可以通过 Kotlin 获得更高阶的函数),但仍然是相同的方法。

    看看这个项目(特别是adapter

    注意它接受 ThingClickListener,但在内部使用 ToggleListener 在 ViewHolders 和 Adapter 之间进行通信。

    最后,这可以简化为使用更少的界面。

    我希望这可以澄清这个想法。 请注意 ViewHolder 和 Adapter 除了处理 Click 侦听器和绑定正确的数据之外,没有任何责任。

    【讨论】:

    • 尤里卡...我想我明白了。非常感谢。在适配器中单击是否是一种好习惯。我有点困惑。例如。看起来这两种方式都是可能的。 stackoverflow.com/questions/67054751/…
    • 是的,在某处“需要”处理点击,这意味着必须有人在一个或多个视图中“设置”点击监听器。但是每一层的职责是不同的(以及“传递/冒泡”)。
    猜你喜欢
    • 2018-06-12
    • 2020-06-04
    • 1970-01-01
    • 2021-03-23
    • 1970-01-01
    • 2017-03-10
    • 2016-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多