萌萌の初音
萌萌の初音
发布于 2022-09-14 / 1082 阅读
0

通过ConcatAdapter实现单RecyclerView不同的效果

之前介绍了通过多ViewHolder实现单列表不同的效果,这次我们通过另一个方法实现同样的效果,那就是RecyclerView 1.2.0版本推出的ConcatAdapter 适配器。

什么是ConcatAdapter?

ConcatAdapter是RecyclerView 1.2.0版本推出的新适配器,它可以将多个RecyclerView.Adapter组合在一起实现viewtype的效果,更方便;

RecyclerView 1.2.0版本更新了什么?

  1. ConcatAdapter 适配器;
  2. 弃用了 ViewHolder.getAdapterPosition取而代之的是getBindingAdapterPosition 会返回相对于其所绑定的适配器的位置、getAbsoluteAdapterPosition 会返回相对于整个 RecyclerView 的位置;
  3. 延迟状态恢复:现在,RecyclerView 适配器可以推迟状态恢复的时间,直到其内容加载完毕;

ConcatAdapter如何使用?

一. 依赖库引入
implementation 'androidx.recyclerview:recyclerview:1.2.1'
二. 将之前写的ViewHolder转化成Adapter
  1. 底部加载adapter样式
class OneAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {

  inner class LoadMoreViewHolder(view: View): RecyclerView.ViewHolder(view) {}

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = LoadMoreViewHolder(
    LayoutInflater.from(parent.context).inflate(R.layout.item_load_more, parent, false))

  override fun onBindViewHolder(holder: ViewHolder, position: Int) {}

  override fun onViewAttachedToWindow(holder: ViewHolder) {
    super.onViewAttachedToWindow(holder)
    (holder.itemView.layoutParams as StaggeredGridLayoutManager.LayoutParams).isFullSpan = true
  }

  override fun getItemCount(): Int = 1
}
  1. 顶部横向滚动adapter
class TwoAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = Type0ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_top_list, parent, false))

  override fun onBindViewHolder(holder: ViewHolder, position: Int) {
  }

  override fun getItemCount(): Int = 1

  override fun onViewAttachedToWindow(holder: ViewHolder) {
    super.onViewAttachedToWindow(holder)
    (holder.itemView.layoutParams as StaggeredGridLayoutManager.LayoutParams).apply {
      isFullSpan = true
      width = holder.itemView.context.resources.displayMetrics.widthPixels
      holder.itemView.layoutParams = this
    }
  }

  inner class Type0ViewHolder(view: View): RecyclerView.ViewHolder(view) {
    init {
      val recyclerView = itemView.findViewById<RecyclerView>(R.id.top_list)
      recyclerView.layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
      LinearSnapHelper().apply {
        attachToRecyclerView(recyclerView)
      }
      recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        /**
         * Called when RecyclerView needs a new [ViewHolder] of the given type to represent
         * an item.
         *
         *
         * This new ViewHolder should be constructed with a new View that can represent the items
         * of the given type. You can either create a new View manually or inflate it from an XML
         * layout file.
         *
         *
         * The new ViewHolder will be used to display items of the adapter using
         * [.onBindViewHolder]. Since it will be re-used to display
         * different items in the data set, it is a good idea to cache references to sub views of
         * the View to avoid unnecessary [View.findViewById] calls.
         *
         * @param parent The ViewGroup into which the new View will be added after it is bound to
         * an adapter position.
         * @param viewType The view type of the new View.
         *
         * @return A new ViewHolder that holds a View of the given view type.
         * @see .getItemViewType
         * @see .onBindViewHolder
         */
        override fun onCreateViewHolder(
          parent: ViewGroup,
          viewType: Int
        ): RecyclerView.ViewHolder = Type3ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false))

        /**
         * Called by RecyclerView to display the data at the specified position. This method should
         * update the contents of the [ViewHolder.itemView] to reflect the item at the given
         * position.
         *
         *
         * Note that unlike [android.widget.ListView], RecyclerView will not call this method
         * again if the position of the item changes in the data set unless the item itself is
         * invalidated or the new position cannot be determined. For this reason, you should only
         * use the `position` parameter while acquiring the related data item inside
         * this method and should not keep a copy of it. If you need the position of an item later
         * on (e.g. in a click listener), use [ViewHolder.getAdapterPosition] which will
         * have the updated adapter position.
         *
         * Override [.onBindViewHolder] instead if Adapter can
         * handle efficient partial bind.
         *
         * @param holder The ViewHolder which should be updated to represent the contents of the
         * item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         */
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}

        /**
         * Returns the total number of items in the data set held by the adapter.
         *
         * @return The total number of items in this adapter.
         */
        override fun getItemCount(): Int = 5

        /**
         * Called when a view created by this adapter has been attached to a window.
         *
         *
         * This can be used as a reasonable signal that the view is about to be seen
         * by the user. If the adapter previously freed any resources in
         * [onViewDetachedFromWindow][.onViewDetachedFromWindow]
         * those resources should be restored here.
         *
         * @param holder Holder of the view being attached
         */
        override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
          super.onViewAttachedToWindow(holder)
          val lp = holder.itemView.layoutParams
          val width = holder.itemView.context.resources.displayMetrics.widthPixels / 1.5
          lp.width = width.toInt()
          lp.height = (width / 3 * 4).toInt()
          holder.itemView.layoutParams = lp
        }
      }
    }
  }

  inner class Type3ViewHolder(view: View): RecyclerView.ViewHolder(view) {}
}
  1. 瀑布流主adapter
class ThreeAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = Type3ViewHolder(
    LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false))

  override fun onBindViewHolder(holder: ViewHolder, position: Int) {}

  override fun getItemCount(): Int = 30

  override fun onViewAttachedToWindow(holder: ViewHolder) {
    super.onViewAttachedToWindow(holder)
    val lp = holder.itemView.layoutParams
    val width = holder.itemView.context.resources.displayMetrics.widthPixels / 2
    lp.width = width
    lp.height = width / 3 * 4
    holder.itemView.layoutParams = lp
  }

  inner class Type3ViewHolder(view: View): RecyclerView.ViewHolder(view) {}
}
  1. 单个banner
class FourAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = Type1ViewHolder(
    LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false))

  override fun onBindViewHolder(holder: ViewHolder, position: Int) {}

  override fun getItemCount(): Int = 3

  override fun onViewAttachedToWindow(holder: ViewHolder) {
    super.onViewAttachedToWindow(holder)
    (holder.itemView.layoutParams as StaggeredGridLayoutManager.LayoutParams).apply {
      isFullSpan = true
      width = holder.itemView.context.resources.displayMetrics.widthPixels
      height = width / 4
      holder.itemView.layoutParams = this
    }
  }

  inner class Type1ViewHolder(view: View): RecyclerView.ViewHolder(view) {}
}
三. ConcatAdapter的使用
    val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
    recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)

    recyclerView.adapter = ConcatAdapter(
      TwoAdapter(),
      FourAdapter(),
      ThreeAdapter(),
      OneAdapter(),
    )

只需要将我们不同的adapter放入ConcatAdapter中即可实现;

ConcatAdapter还有带config的构造方法,isolateViewTypes用于不同adapter是否共享viewtype;stableIdMode为ConcatAdapter运作方式,adapter是否隔离,meigeadapter是否有单独的id等;

完整项目地址

github:github-RecyclerViewStyle
gitlab:gitlab-RecyclerViewStyle