如何处理嵌套 RecyclerView 中的点击

Poi*_*hat 4 android interface listener android-fragments android-recyclerview

有人可以解释一下如何处理这个问题的逻辑:

我有一个片段,在 WebSocket 调用之后会膨胀 2 个 Recyclerviews。

Child Recyclerview 嵌套在 Parent Recyclerview 中,父适配器调用子适配器。我想为单击侦听器放置一个接口,该接口处理 Fragment 中子项中的单击。

我应该把接口放在哪里,哪个类应该实现它?

Mar*_*ini 6

您尝试做的事情已经进行了多次。

您可以尝试多种方法,但一般而言,职责如下所示:

YourContext (片段/活动)

  • 使用RecyclerView.
  • 实例化 YourAdapter
  • 订阅、请求、等待您的数据并将其传递到YourAdapter.
  • 维护一个用于点击处理的界面,例如:
interface YourThingClickHandler {
   fun onThingWasClicked(thing: Thing) // and any other thing you may need.
}
Run Code Online (Sandbox Code Playgroud)
  • 可以YourContext: YourThingClickHandler或者如果你愿意,你可以保留一个匿名/本地实例。我通常先做前者,然后fun onThingWasClicked(...)在片段/活动中实现,这取决于单击项目时您需要做什么。

YourAdapter

  • 预计列表Things 一个YourThingClickHandler实例。因此,在您的 Fragment/Activity 中,您会这样做,例如(伪代码):
// This is called once your ViewModel/Presenter/Repository/etc. makes the data available.
fun onThingsLoaded(things: List<Thing>) {
    adapter.setClickHandler(this) // this can be passed when you construct your adapter too via constructor like adapter = YourAdapter(this)
    adapter.submitList(things) // if the adapter extends a `ListAdapter` this is all you need.
}
Run Code Online (Sandbox Code Playgroud)

现在您已经传递了一个外部点击处理程序,您需要处理内部列表。现在您有几个选择: 1. 一直传递相同的点击处理程序并让innerAdapter直接与此对话。2. 将outerAdapter行为作为innerAdapter 中发生的点击之间的中间件,并通过您刚刚提供的这个点击处理程序将它们冒泡。

你选择哪一个,很大程度上取决于你想用它做什么,以及你想如何处理它。在我看来没有对与错。

不管你做什么,你仍然需要从视图持有者到这个点击处理程序......

所以YourAdapter你应该有另一个接口:

    interface InternalClickDelegate {
        fun onItemTappedAt(position: Int)
    }
Run Code Online (Sandbox Code Playgroud)

这个内部处理程序将用于从 viewHolder 对话,回到您的适配器,并将点击冒泡到外部点击处理程序。

现在你可以有一个这样的实例,在你的适配器类中像这样定义(记住这是伪代码):

    private val internalClickHandler: InternalClickDelegate? = object : InternalClickDelegate {

        override fun onItemTappedAt(position: Int) {
            externalClickHandler?.run {
                onThingWasClicked(getItem(position))
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,如果外部单击处理程序(YourThingClickHandler您提供的)不为空,则从适配器的数据源中获取该项目,并将其传递。

您如何将这个内部处理程序与每个视图持有者连接起来?

当你这样做时onCreateViewHolder,有一个 ViewHolder 需要......你猜对了,一个InternalClickDelegate实例等等......

 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        // decide which viewHolder you're inflating... and...

       return YourViewHolder(whateverViewYouInflate, internalClickHandler)

Run Code Online (Sandbox Code Playgroud)

现在你的 ViewHolder(s) 有一个对这个内部点击处理程序的引用......

因此,当您这样做时,onBindViewHolder(...)您可能会调用您选择的通用 ViewHolder 方法,例如,如果您的 View 持有者可以是不同类型,那么您可能有一个抽象 viewHolderfun bind(thing: Thing)方法或类似方法,每个具体的 viewHolder 子类型都必须实现...在那里,你会做这样的事情:

override fun bind(thing: Thing) {
   if (clickHandler != null) {
       someViewYourViewHolderInflated.setOnClickListener(this) // this assumes your ViewHolder implements View.OnClickListener from the framework
   }
}

Run Code Online (Sandbox Code Playgroud)

因为您的 ViewHolder 实现了View.OnClickListener,所以您必须在其中实现 onClick 方法:

    override fun onClick(v: View?) {
        clickHandler?.onItemTappedAt(adapterPosition)
    }

Run Code Online (Sandbox Code Playgroud)

这就是您的 ViewHolder 将在onClick方法中接收来自 Android 的点击/点击事件的方式,如果您提供了点击处理程序(您在传递 internalClickHandler 时在适配器 onCreateViewHolder 中做了),它将简单地使水龙头冒泡,传递位置. adapterPosition是 Kotlin 中调用getAdapterPosition()RecyclerView 适配器的等价物。

太长了,没看图

  • Fragment : ExternalClickListener -> 将它的一个实例传递给适配器。
  • Adapter:接收 ExternalClickListener,将 InternalClickListener 传递给每个 ViewHolder。
  • ViewHolder:接收内部点击侦听器,将自身设置为可点击(无论是整个itemView小部件还是您想要使其可点击的任何小部件,如果您希望整个单元格都可点击,只需使用itemViewViewHolder 的“整体”视图。

当 viewHolder 的视图被点击时,android 调用点击监听器的onClick方法。在那里,因为您在 ViewHolder 中,您可以执行 getAdapterPosition() 并将其传递给internal您收到的点击处理程序。

然后 Adapter 可以将该位置转换回数据,并且因为您提供了一个 External clickListener,它可以将实际项目传回给外部点击侦听器。

等等,但是 NESTED RecyclerView 怎么样。

这没什么特别的,你只需要提供相同的机制,并不断地传递东西。你做什么或者你有多少这样的接口,完全取决于你想要实现什么;就像我一开始说的,每个解决方案都是不同的,在做出架构决策时必须考虑其他因素。

一般而言,请记住以下几点关注分离:保持小事一桩。例如:拥有这个双界面可能看起来很疯狂,但很清楚每个界面的作用。内部的,只关心“视图”中的“点击”,并提供列表中发生所述点击的位置。

这就是适配器获取数据并对真正挖掘的项目做出明智的猜测所需的“全部”。

片段不知道(或关心)“位置”,这是适配器的实现细节;positions存在的事实,对 Fragment 一无所知;但是 Fragment 很高兴,因为它Thing在回调中接收,这是最有可能需要知道的(如果您出于任何原因需要知道位置,请裁剪和修改externalCallback以具有您选择的签名。

现在将“传递手”从您的 OuterAdapter 复制到您的 InnerAdapter,您已经完成了您想做的事情。

祝你好运!