Last active
July 5, 2022 06:47
-
-
Save kimji1/b28ba9f4a84418cc2929250a00d02c9d to your computer and use it in GitHub Desktop.
Files for Sticky Header Implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class BaseListAdapter<T: ListItem, VH : DataBindingBaseViewHolder<T>> | |
: ListAdapter<T, VH>(ListItemDiffUtils()) { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class DataBindingBaseViewHolder<T : ListItem>( | |
open val binding: ViewDataBinding | |
) : RecyclerView.ViewHolder(binding.root) { | |
abstract fun onBind(item: T) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface ListItem { | |
fun getViewType(): Int | |
fun areItemsTheSame(newItem: ListItem): Boolean | |
fun areContentsTheSame(newItem: ListItem): Boolean | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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? | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface StickyHeaderListItem : ListItem { | |
fun isHeader(): Boolean | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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