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