Skip to content

Instantly share code, notes, and snippets.

@d4vidi
Last active September 20, 2018 11:30
Show Gist options
  • Save d4vidi/571af9c2ee1db4a5bc15c20dafcdcd8e to your computer and use it in GitHub Desktop.
Save d4vidi/571af9c2ee1db4a5bc15c20dafcdcd8e to your computer and use it in GitHub Desktop.
package com.d4vidi
import android.support.annotation.Px
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.PagerSnapHelper
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewTreeObserver
data class VisiblePageState(
var index: Int,
var view: View,
@Px var viewCenterX: Int,
@Px var distanceToSettledPixels: Int,
var distanceToSettled: Float)
interface RVPagerStateListener {
fun onPageScroll(pagesState: List<VisiblePageState>) {}
fun onScrollStateChanged(state: Int) {}
fun onPageSelected(index: Int) {}
}
open class RVPagerSnapHelperListenable(private val maxPages: Int = 3) : RecyclerView.OnScrollListener() {
fun attachToRecyclerView(recyclerView: RecyclerView, listener: RVPagerStateListener) {
this.assertRecyclerViewSetup(recyclerView)
setUpSnapHelper(recyclerView, listener)
setUpScrollListener(recyclerView, listener)
}
protected fun setUpScrollListener(recyclerView: RecyclerView, listener: RVPagerStateListener) {
PagerSnapOnScrollListener(recyclerView, listener, maxPages)
}
protected fun setUpSnapHelper(recyclerView: RecyclerView, listener: RVPagerStateListener) {
PagerSnapHelperListenable(recyclerView, listener).attachToRecyclerView(recyclerView)
}
protected fun assertRecyclerViewSetup(recyclerView: RecyclerView) {
if (recyclerView.layoutManager !is LinearLayoutManager) {
throw IllegalArgumentException("RVPagerStateListenerSubscriber can only work with a linear layout manager")
}
if ((recyclerView.layoutManager as LinearLayoutManager).orientation != LinearLayoutManager.HORIZONTAL) {
throw IllegalArgumentException("RVPagerStateListenerSubscriber can only work with a horizontal orientation")
}
}
}
open class PagerSnapHelperListenable(protected val recyclerView: RecyclerView, val externalListener: RVPagerStateListener)
: PagerSnapHelper()
, ViewTreeObserver.OnGlobalLayoutListener {
var lastPage = RecyclerView.NO_POSITION
init {
recyclerView.viewTreeObserver.addOnGlobalLayoutListener(this)
}
override fun onGlobalLayout() {
val position = (recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (position != RecyclerView.NO_POSITION) {
notifyNewPageIfNeeded(position)
recyclerView.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
val view = super.findSnapView(layoutManager)
notifyNewPageIfNeeded(recyclerView.getChildAdapterPosition(view))
return view
}
override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int {
val position = super.findTargetSnapPosition(layoutManager, velocityX, velocityY)
notifyNewPageIfNeeded(position)
return position
}
protected fun notifyNewPageIfNeeded(page: Int) {
if (page != lastPage) {
this.externalListener.onPageSelected(page)
lastPage = page
}
}
}
open class PagerSnapOnScrollListener(val recyclerView: RecyclerView, val externalListener: RVPagerStateListener, maxPages: Int) : RecyclerView.OnScrollListener() {
var pageStates: MutableList<VisiblePageState> = ArrayList(maxPages)
var pageStatesPool = List(maxPages, { _ -> VisiblePageState(0, recyclerView, 0, 0, 0f) })
init {
recyclerView.addOnScrollListener(this)
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
val layoutManager = recyclerView!!.layoutManager as LinearLayoutManager
val firstPos = layoutManager.findFirstVisibleItemPosition()
val lastPos = layoutManager.findLastVisibleItemPosition()
val screenEndX = recyclerView.context.resources.displayMetrics.widthPixels
val midScreen = (screenEndX / 2)
for (position in firstPos..lastPos) {
val view = layoutManager.findViewByPosition(position)
val viewWidth = view.measuredWidth
val viewStartX = view.x
val viewEndX = viewStartX + viewWidth
if (viewEndX >= 0 && viewStartX <= screenEndX) {
val viewHalfWidth = view.measuredWidth / 2f
val pageState = pageStatesPool[position - firstPos]
pageState.index = position
pageState.view = view
pageState.viewCenterX = (viewStartX + viewWidth / 2f).toInt()
pageState.distanceToSettledPixels = (pageState.viewCenterX - midScreen)
pageState.distanceToSettled = (pageState.viewCenterX + viewHalfWidth) / (midScreen + viewHalfWidth)
pageStates.add(pageState)
}
}
externalListener.onPageScroll(pageStates)
// Clear this in advance so as to avoid holding refs to views.
pageStates.clear()
}
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
externalListener.onScrollStateChanged(newState)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment