ConcatAdapter 是 recyclerview: 1.2.0-alpha 04 中提供的一个新组件,它能够帮咱们顺序地组合多个 Adapter,并让它们显示在同一个 RecyclerView 中。这使您能够更好地封装 Adapter。您没必要再将许多数据源组合到一个 Adapter 中,从而在减小 Adapter 复杂度的同时也让它们能够被复用。android
这方面的一个用例,是在列表头部和底部显示加载状态: 当列表从网络中检索数据时,咱们想显示一个加载中的图标;若是出现错误,咱们要显示错误信息和重试按钮。git
△ 一个带有底部的 RecyclerView,底部显示了加载状态: 加载进度或错误信息github
ConcatAdapter 让咱们能够顺序显示多个 Adapter 中的内容。例如,假设咱们有下面三个 Adapter:网络
val firstAdapter: FirstAdapter = … val secondAdapter: SecondAdapter = … val thirdAdapter: ThirdAdapter = … val concatAdapter= ConcatAdapter(firstAdapter, secondAdapter, thirdAdapter) recyclerView.adapter = concatAdapter
RecyclerView
将会按 Adapter 顺序显示全部的项目。ide
使用不一样的适配器可使您更好地区分列表的每一个部分。例如,若是要显示一个头部,能够将其封装在它本身的 Adapter 中,而无需把头部的逻辑与处理列表显示的 Adapter 混杂在一块儿。函数
△ RecyclerView 和 Adapter 数据布局
咱们能够在头部或底部显示一个进度条或错误信息。列表成功加载数据后,头部或底部便不该该再显示任何信息。这样一来,它们就能够用 Adapter 实现有 0 个或 1 个项目的列表:动画
val concatAdapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter) recyclerView.adapter = concatAdapter
若是头部和底部用的是同一布局、ViewHolder 和 UI 逻辑 (例如: 进度条要什么时候显示、怎么显示),您能够只实现一个 Adapter,而后建立两个实例: 一个做为头部、一个做为底部。google
要得到完整的实现,请查看这里 拉取请求,它添加了:spa
默认状况下,每一个 Adapter 维护它们本身的 ViewHolder 池,在 Adapter 之间不会进行复用。但若是多个 Adapter 使用的是同一种 ViewHolder,咱们可能会想要在 Adapter 间复用 ViewHolder 的实例。咱们能够在构造 ConcatAdapter 时使用一个 ConcatAdapter.Config 对象来实现这样的效果。只要设置 isolateViewTypes = false,就可让全部合并进来的 Adapter 使用同一个视图池。在显示加载状态的头部和底部的例子中,两种 ViewHolder 事实上使用的是相同的内容,因此咱们能够复用它们。
⚠️ 若是要支持不一样的 ViewHolder 类型,您应该实现 Adapter.getItemViewType) 方法。当您复用 ViewHolder 时,确保同一视图类型没有对应不一样的 ViewHodler!防止出现这个问题的最佳实践之一,即是将布局 ID 做为视图类型返回。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class HeaderAdapter() : RecyclerView.Adapter<LoadingStateViewHolderHeaderViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LoadingStateViewHolder(parent) } override fun getItemViewType(position: Int): Int { - return 0 + return R.layout.list_loading } } class FooterAdapter() : RecyclerView.Adapter<LoadingStateViewHolderFooterViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LoadingStateViewHolder(parent) } override fun getItemViewType(position: Int): Int { - return 0 + return R.layout.list_loading } }
相比于使用 stable id 搭配 notifyDataSetChanged,咱们更建议使用 Adapter 的特定通知事件,该事件能够为RecyclerView 提供更多有关数据集更改的信息,从而使 RecyclerView 能够更有效率地更新 UI,同时也有更好的动画效果。若是您正在使用 ListAdapter 的话,其内部会使用 DiffUtil 回调帮您处理通知事件。可是若是您须要使用 stable id,ConcatAdapter.Config 为其提供了三种不一样的配置: NO_STABLE_IDS、ISOLATED_STABLE_IDS 和 SHARED_STABLE_IDS。其中后面两种须要您本身处理 Adapter 中的 stable id。您能够查看 StableIdMode 文档来得到更多关于其工做原理的信息。
当 ConcatAdapter
中的一个 Adapter 调用了通知函数时,ConcatAdapter
会在更新 RecyclerView
以前计算新的项目位置。
从 RecyclerView
的角度来看,notifyItemRangeChanged 表示更新的项目相同,只是内容有所更改;notifyDataSetChanged 表示先后数据之间没有任何关系。所以,咱们没法将 notifyDataSetChanged 映射到 notifyItemRangeChanged
中。
若是一个 Adapter 调用了 Adapter.notifyDataSetChanged
,则 ConcatAdapter
也会调用Adapter.notifyDataSetChanged,而不是 Adapter.notifyItemRangeChanged。与 RecyclerViews 同样,咱们要选择更精细的更新操做,通常状况下避免调用 Adapter.notifyDataSetChanged()。也可使用自动执行此操做的 Adapter 实现,例如 ListAdapter 或 SortedList。
您可能使用过 ViewHolder.getAdapterPosition) 来得到 Adapter 中某个 ViewHolder
的位置。如今,由于咱们合并了多个 Adapter,做为代替,您须要调用 ViewHolder.getBindingAdapterPosition())。若是您想在共享 ViewHolder
的状况下得到最后一个绑定某个 ViewHolder
的 Adapter,可使用 ViewHolder.getBindingAdapter())。
以上就是所有了!总结一下: 若是要顺序显示不一样类型的数据的同时,也但愿这些数据可以封装在它们本身的 Adapter 中,请开始使用 ConcatAdapter
;若是想要更进一步对 ViewHolder
池和 statle id 进行高级控制,则要使用 ConcatAdapter.Config
。