Last active
September 20, 2018 11:30
-
-
Save d4vidi/571af9c2ee1db4a5bc15c20dafcdcd8e to your computer and use it in GitHub Desktop.
This file contains 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
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