【问题标题】:Nested Recyclerviews with Complex Room LiveData具有复杂房间 LiveData 的嵌套 Recyclerviews
【发布时间】:2019-07-03 18:01:28
【问题描述】:

我有一个父对象的集合,每个父对象都有一个子对象的集合。请致电ParentModels 和 ChildModels。

我想在屏幕上显示渲染的 ParentModel 的 RecyclerView,每个都包含渲染的 ChildModel 的 RecyclerView。

希望避免一个神 LiveData 仅仅因为一个 ChildModel 的一个属性发生变化而重绘所有内容,我打算将它们分开。

我不知道如何使用 Recyclerview 适配器和持有者以及我需要的任何 Fragment 和 ViewModel 来构建它。现在我有

class MyFragment: Fragment() {
  private lateinit val mViewModel: FragmentViewModel
  // ...
  fun onViewCreated(/*...*/) {
    val parentAdapter = ParentAdapter()
    view.findViewById<RecyclerView>(/*...*/).apply {
      adapter = parentAdapter
      //...
    }
    viewModel.getParents().observe(this, Observer {
      parentAdapter.setParents(it)
    }
  }
}

class FragmentViewModel @Inject constructor(repository: RoomRepo): ViewModel() {
  mParents: LiveData<List<ParentModel>> = repository.getParents()
  fun getParents() = mParents
  //...
}

class ParentAdapter: RecyclerView.Adapter<ParentHolder>() {
  private lateinit var mParents: List<ParentModel>
  fun setParents(list: List<ParentModel>) {
    mParents = list
    notifyDataSetChanged()
  }
  override fun onCreateViewHolder(parent: ViewGroup, /*...*/) {
    return ParentHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent, parent, false))
  }
  override fun onBindViewHolder(holder: ParentHolder, position: Int) {
    holder.bind(/*UNKNOWN*/)
  }
  // ...
  inner class ParentHolder(private val mView: View): RecyclerView.ViewHolder(mView) {
    fun bind(/*UNKNOWN*/) {
      // WHAT TO DO HERE???
    }
  }
}

加上我的R.layout.parent(我省略了其他不相关的东西,比如只绘制一条水平线的视图,但这就是我将 RecyclerView 嵌套在 LinearLayout 中的原因):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  android:orientation="vertical"
  android:layout_height="wrap_content"
  android:layout_width="match_parent"
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools">

  <androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

我不假思索地写了一个 ChildAdapter、ChildHolder 和其他一些东西,因为我认为这将是微不足道的实现,但在这一点上,我脑子里有些东西,我可能看不到明显的东西。

我已经根据基础数据正确加载了第一个 RecyclerView。但是这个父recyclerview也需要:

  1. 根据单个 parent.id 获取子项
  2. 为显示子级的单个父级 recyclerview 项创建子级 recyclerview

Room 从函数 repository.getChildrenByParentId(id: Long) 返回一个 LiveData>。这就是我正在使用的数据。

但是我在哪里获取这个,我如何将它挂钩到属于父 recyclerview 的相关子 recyclerview?

我不想有一个上帝片段 viewModel.getParents().observe(...) { parentAdapter.update(it) } 和 also 必须做某种 viewModel.getChildren().observe(...) { parentAdapter.更新儿童(它)}

因为这会破坏关注点分离。在我看来,父 recyclerview 中的每个项目都应该有一个 viewmodel 来获取属于它的孩子,然后创建一个 recyclerview 并使用 ChildAdapter 来显示这些孩子,但我似乎无法弄清楚在哪里插入ChildFragment 和 ChildViewModel(其中包含 repository.getChildrenByParentId)让这一切正常工作。

我在网上找到的所有示例似乎都没有帮助,因为他们使用了没有 LiveData 的人为示例以及将所有内容放在单个适配器中的上帝片段/活动。

【问题讨论】:

    标签: android kotlin android-recyclerview android-livedata


    【解决方案1】:

    我确实有 1 个可以渲染所有内容的适配器,使用 DiffUtil(或其异步版本)类来确保我不会(并且我引用)“重绘所有内容只是因为一个 ChildModel 的一个属性变化”。

    我会将构建(和提供)数据的复杂责任转移到您的存储库(或者,如果您希望它更接近,则转移到您的 ViewModel 作为 1 个或多个之间的协调器(我不知道如何你的模型看起来,所以我只是想象)存储库提供数据。

    这将允许您向 ui 提供一个更精心策划的不可变 ParentsAndChildren 列表,并且您的 RecyclerView/Adapter 的职责突然变得更加简单,显示它并绑定正确的视图每一行。您的 UI 突然变得更快,在主线程上花费的时间更少,您甚至可以对创建此列表的逻辑进行单元测试,完全独立于您的 Activity/Fragment。

    我认为ParentsAndChildren 是这样的:

    class ParentChildren(parent: Parent?, children: Children?)

    然后,当 parent 不为 null 而 children 为 null 时,您的绑定可以膨胀一个视图。当 children 不为 null 时,您知道它是一个子项(您也可以包括父项,这取决于您如何构建此数据)。问题在这里解决了,你的适配器看起来像

    class YourAdapter : ListAdapter<ParentChildren, RecyclerView.ViewHolder>(DiffUtilCallback()) {
    
    ...
    
    

    你需要实现你的DiffUtilCallback():

    internal class DiffUtilCallback : DiffUtil.ItemCallback&lt;ParentChildren&gt;() { 及其两种方法(areContentsTheSameareItemsTheSame)。

    还有你的适配器的两种方法:

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            val inflater = LayoutInflater.from(parent.context)
    
            return when (viewType) {
                viewTypeParent -> YourParentViewHolder(inflater.inflate(R.layout.your_layout_for_parent), parent, false))
                viewTypeChildren -> YourChildrenViewHolder(inflater.inflate(R.layout.your_layout_for_children), parent, false))
                else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
            }
        }
    

    我将有一个抽象基础来进一步简化适配器:

      internal abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            abstract fun bind(data: ParentChildren)
        }
    
    

    这可以让你拥有你的

        // I'm writing pseudo code here... keep it in mind
        internal class ParentViewHolder(itemView: View) : BaseViewHolder(itemView) {
            private val name: TextView = itemView.findViewById(R.id.item_text)
    
            override fun bind(data: ParentChildren) {
                name.text = parentChildren.parent?.name
            }
        }
    
        internal class ChildrenViewHolder(itemView: View) : BaseViewHolder(itemView) {
            private val name: TextView = itemView.findViewById(R.id.item_text)
    
            override fun bind(data: ParentChildren) {
                name.text = parentChildren.children?.name
            }
        }
    
    

    你明白了。

    现在...ListAdapter&lt;&gt; 有一个名为 submitList(T) 的方法,其中 T 是上述伪示例中适配器 ParentChildren 的类型。

    就我而言,现在你必须提供这个 Activity 或 Fragment 来托管这个适配器,通过 LiveData 或 你喜欢的任何东西来提供你所拥有的架构的列表。

    它可以是一个存储库,将其传递给 viewModel 内的 MutableLiveData 和暴露 LiveData&lt;List&lt;ParentChildren&gt; 或类似于 UI 的 ViewModel。

    天空是极限。

    这改变了将这些数据放在一起的复杂性,更接近数据所在的位置,并且 SQL/Room 的强大功能可以利用您如何组合和处理这些数据,而不管 UI 需要或想用它做什么。

    这是我的建议,但基于我对您项目的了解非常有限。

    祝你好运! :)

    【讨论】:

    • 这就是我最终要做的。效果很好!不知道 DiffUtil
    • 很高兴听到! :) 祝你好运!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-09
    • 1970-01-01
    • 2016-08-11
    • 2014-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多