4

I'd like to headers in a list using the android.arch.paging components.

enter image description here

Usually this would easy enough, just add different types for a RecyclerView adapter to handle and create a new list from items including headers.

But with the paging components I'm essentially handing the PagedList a PositionalDataSource from an SQL query. Is it possible to interrupt this and add header types?

Matthew Shearer
  • 2,715
  • 3
  • 23
  • 32

1 Answers1

4

How I ended up solving this for anyone interested.

when binding the data I pass in the previous item as well

  override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = getItem(position)
    val previousItem = if (position == 0) null else getItem(position - 1)
    holder.bind(item, previousItem)
  }

Every view then sets a header, which is only made visible if the previous item doesn't have the same header.

    val previousHeader =  previousItem?.name?.capitalize().first()
    val header = item?.name?.capitalize()?.first()
    view.cachedContactHeader.text = header
    view.cachedContactHeader.isVisible  = previousHeader != header

Update 24/01/20

Since answering this I have changed to using a custom ItemDecoration

class StickyHeaderDividerItem(
  context: Context,
  private val stickyHeaderCallbacks: StickyHeaderCallbacks
) : ItemDecoration() {

  private val headerHeight = context.resources.getDimensionPixelOffset(R.dimen.sticky_header_height)
  private var headerView: View? = null
  private var headerTextView: TextView? = null
  private var headerEndTextView: TextView? = null

  override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: State) {
    super.getItemOffsets(outRect, view, parent, state)

    val position = parent.getChildAdapterPosition(view)
    if (position >= 0 && stickyHeaderCallbacks.isHeader(position)) {
      outRect.top = headerHeight
    }
  }

  override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: State) {
    super.onDrawOver(canvas, parent, state)

    if (headerView == null) {
      headerView = (parent.inflate(R.layout.list_item_sticky_header)).apply {
        fixLayoutSize(this, parent)
      }
      headerTextView = headerView?.headerText
      headerEndTextView = headerView?.headerEndText
    }

    var previousHeader = ""
    parent.children.toList().forEach { child ->
      val position = parent.getChildAdapterPosition(child)
      val headerTitle = position.takeIf { it >= 0 }?.let(stickyHeaderCallbacks::headerTitle).orEmpty()
      val headerEndTitle = position.takeIf { it >= 0 }?.let(stickyHeaderCallbacks::headerTitleEnd)
      headerTextView?.text = headerTitle
      headerEndTextView?.text = headerEndTitle

      if ((previousHeader != headerTitle) || (position >= 0 && stickyHeaderCallbacks.isHeader(position))) {
        headerView?.let { drawHeader(canvas, child, it) }
        previousHeader = headerTitle
      }
    }
  }

  private fun drawHeader(canvas: Canvas, child: View, headerView: View) {
    canvas.run {
      save()
      translate(0F, maxOf(0, child.top - headerView.height).toFloat())
      headerView.draw(this)
      restore()
    }
  }

  private fun fixLayoutSize(view: View, parent: ViewGroup) {
    val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
    val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
    val childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingStart + parent.paddingEnd, view.layoutParams.width)
    val childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.paddingTop + parent.paddingBottom, view.layoutParams.height)
    view.measure(childWidth, childHeight)
    view.layout(0, 0, view.measuredWidth, view.measuredHeight)
  }
}



interface StickyHeaderCallbacks {

  fun isHeader(itemPosition: Int): Boolean
  fun headerTitle(itemPosition: Int): String
  fun headerTitleEnd(itemPosition: Int): String? = null
}
Matthew Shearer
  • 2,715
  • 3
  • 23
  • 32
  • any idea how to solve this issue if you have multiple items in a row, like a gallery grid? – kilian eller Jun 27 '19 at 14:44
  • no sorry. I ended up removing the headers entirely as it's not a great implementation. – Matthew Shearer Jul 01 '19 at 08:42
  • I tried making the headers the same size like the items but it didn't work, now i will try to overwrite the AdapterDataObserver like here https://github.com/googlesamples/android-architecture-components/issues/548 – kilian eller Jul 01 '19 at 22:23