Skip to content

Instantly share code, notes, and snippets.

@kimji1
Last active July 5, 2022 06:47
Show Gist options
  • Save kimji1/b28ba9f4a84418cc2929250a00d02c9d to your computer and use it in GitHub Desktop.
Save kimji1/b28ba9f4a84418cc2929250a00d02c9d to your computer and use it in GitHub Desktop.
Files for Sticky Header Implementation
abstract class BaseListAdapter<T: ListItem, VH : DataBindingBaseViewHolder<T>>
: ListAdapter<T, VH>(ListItemDiffUtils()) {
}
abstract class DataBindingBaseViewHolder<T : ListItem>(
open val binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root) {
abstract fun onBind(item: T)
}
interface ListItem {
fun getViewType(): Int
fun areItemsTheSame(newItem: ListItem): Boolean
fun areContentsTheSame(newItem: ListItem): Boolean
}
abstract class StickyHeaderAdapter<T : StickyHeaderListItem, VH : StickyHeaderViewHolder<T>> :
BaseListAdapter<T, VH>(), StickyHeaderDecoration.SectionCallback {
override fun isHeader(position: Int): Boolean = getItem(position).isHeader()
override fun getHeaderLayoutView(
parent: RecyclerView,
itemPosition: Int
): View? = getHeaderLayoutView(parent, itemPosition, itemPosition)
override fun getHeaderLayoutView(
parent: RecyclerView,
itemPosition: Int,
viewHolderPosition: Int
): View? {
val item = getItem(itemPosition)
if (!item.isHeader()) {
return null
}
val viewHolder = parent
.findViewHolderForAdapterPosition(viewHolderPosition) as? StickyHeaderViewHolder<StickyHeaderListItem>
if (viewHolder?.itemViewType != item.getViewType()) {
return null
}
val view = viewHolder.getHeaderView(parent) ?: return null
return viewHolder.bind(view, getItem(itemPosition))
}
override fun updateHeaderData(
parent: RecyclerView,
currentHeaderView: View,
topChildPosition: Int
): View? {
val headerItemPosition = (0..topChildPosition).toList()
.sortedDescending()
.firstOrNull { getItem(it).isHeader() }
?: return currentHeaderView
val emptyViewHolder = onCreateViewHolder(parent, getItem(headerItemPosition).getViewType())
return emptyViewHolder.bind(currentHeaderView, getItem(headerItemPosition))
}
}
class StickyHeaderDecoration(private val sectionCallback: SectionCallback) :
RecyclerView.ItemDecoration() {
var currentHeader: View? = null
var currentHeaderPosition = RecyclerView.NO_POSITION
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
updateHeaderView(parent)
presentHeaderView(c, parent)
}
private fun updateHeaderView(parent: RecyclerView) {
val topChild = parent.getChildAt(0) ?: return
val topChildPosition = parent.getChildAdapterPosition(topChild)
if (topChildPosition == RecyclerView.NO_POSITION) {
return
}
if (currentHeader != null && currentHeaderPosition != topChildPosition) {
sectionCallback.updateHeaderData(parent, currentHeader!!, topChildPosition)
}
val view = sectionCallback.getHeaderLayoutView(parent, topChildPosition) ?: return
updateCurrentHeaderInfo(parent, view, topChildPosition, topChild)
}
private fun updateCurrentHeaderInfo(
parent: RecyclerView,
newHeaderView: View,
headerViewPosition: Int,
headerItemView: View
) {
currentHeader = newHeaderView
currentHeaderPosition = headerViewPosition
fixLayoutSize(parent, currentHeader!!, headerItemView.measuredHeight)
}
private fun presentHeaderView(c: Canvas, parent: RecyclerView) {
if (currentHeader == null) {
return
}
val contactPoint = currentHeader!!.bottom
val childInContact: View = getChildInContact(parent, contactPoint) ?: return
val childAdapterPosition = parent.getChildAdapterPosition(childInContact)
if (childAdapterPosition == RecyclerView.NO_POSITION) {
return
}
when {
sectionCallback.isHeader(childAdapterPosition) ->
moveHeader(c, currentHeader!!, childInContact)
else ->
drawHeader(c, currentHeader!!)
}
}
private fun getChildInContact(parent: RecyclerView, contactPoint: Int): View? {
var childInContact: View? = null
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
if (child.bottom > contactPoint) {
if (child.top <= contactPoint) {
childInContact = child
break
}
}
}
return childInContact
}
private fun moveHeader(c: Canvas, currentHeader: View, nextHeader: View) {
c.save()
c.translate(0f, nextHeader.top - currentHeader.height.toFloat())
currentHeader.draw(c)
c.restore()
}
private fun drawHeader(c: Canvas, header: View) {
c.save()
c.translate(0f, 0f)
header.draw(c)
c.restore()
}
/**
* Measures the header view to make sure its size is greater than 0 and will be drawn
* https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations
*/
private fun fixLayoutSize(parent: ViewGroup, view: View, height: Int) {
val widthSpec = View.MeasureSpec.makeMeasureSpec(
parent.width,
View.MeasureSpec.EXACTLY
)
val heightSpec = View.MeasureSpec.makeMeasureSpec(
parent.height,
View.MeasureSpec.EXACTLY
)
val childWidth: Int = ViewGroup.getChildMeasureSpec(
widthSpec,
parent.paddingLeft + parent.paddingRight,
view.layoutParams.width
)
val childHeight: Int = ViewGroup.getChildMeasureSpec(
heightSpec,
parent.paddingTop + parent.paddingBottom,
height
)
view.measure(childWidth, childHeight)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
}
interface SectionCallback {
fun isHeader(position: Int): Boolean
fun getHeaderLayoutView(parent: RecyclerView, itemPosition: Int): View?
fun getHeaderLayoutView(
parent: RecyclerView,
itemPosition: Int,
viewHolderPosition: Int
): View?
fun updateHeaderData(
parent: RecyclerView,
currentHeaderView: View,
topChildPosition: Int
): View?
}
}
interface StickyHeaderListItem : ListItem {
fun isHeader(): Boolean
}
abstract class StickyHeaderViewHolder<T : StickyHeaderListItem>(binding: ViewDataBinding) :
DataBindingBaseViewHolder<T>(binding) {
abstract fun getHeaderView(parent: ViewGroup): View?
abstract fun bind(view: View, item: T): View?
}
abstract class StickyContentViewHolder<T : StickyHeaderListItem>(binding: ViewDataBinding) :
StickyHeaderViewHolder<T>(binding) {
override fun getHeaderView(parent: ViewGroup): View? {
return null
}
override fun bind(view: View, item: T): View? {
return null
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment