Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
RecyclerView scrolling
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewTreeObserver
import java.lang.ref.WeakReference
class NoMotionScrollManager(
private val allowCyclicRotation: Boolean = false,
private val viewSelector: ViewSelector = { it }
) {
private var recycler: RecyclerView? = null
private var currentFocus = WeakReference<View>(null)
private var lastPosition = 0
set(value) {
val finalValue = Math.max(0, Math.min((recycler?.adapter?.itemCount ?: 0) - 1, value))
field = finalValue
}
private val focusListener =
ViewTreeObserver.OnGlobalFocusChangeListener { _, newFocus ->
newFocus ?: return@OnGlobalFocusChangeListener
if (newFocus.parentView !is RecyclerView) return@OnGlobalFocusChangeListener
currentFocus = WeakReference(viewSelector(newFocus))
}
private var selectListener: (Int) -> Unit = {}
private var preSelectListener: (Int) -> Unit = {}
val focus get() = currentFocus.get()
fun bind(recycler: RecyclerView) {
this.recycler = recycler.apply {
viewTreeObserver.addOnGlobalFocusChangeListener(focusListener)
}
}
fun unbind() {
recycler?.viewTreeObserver?.removeOnGlobalFocusChangeListener(focusListener)
recycler = null
}
fun setSelectedListener(listener: (Int) -> Unit) = apply { selectListener = listener }
fun setPreSelectedListener(listener: (Int) -> Unit) = apply { preSelectListener = listener }
fun selectNext(selectOnly: Boolean = false) {
val currentFocus = currentFocus.get()
val currentPosition = if (currentFocus?.parent !is RecyclerView) lastPosition//-1
else recycler?.getChildAdapterPosition(currentFocus) ?: -1
val nextPosition = currentPosition + 1
if (selectOnly) selectAt(nextPosition) else focusAt(nextPosition)
}
fun selectPrevious(selectOnly: Boolean = false) {
val currentFocus = currentFocus.get()
val currentPosition = if (currentFocus?.parent !is RecyclerView) lastPosition//1
else recycler?.getChildAdapterPosition(currentFocus) ?: 1
val nextPosition = currentPosition - 1
if (selectOnly) selectAt(nextPosition) else focusAt(nextPosition)
}
fun selectFirst() = focusAt(0)
internal fun focusAt(position: Int) {
preSelectListener(position)
recycler?.focusChildAtPosition(position) {
if (!it && allowCyclicRotation) {
val focusablePosition = if (position <= 0) {
recycler?.adapter?.itemCount?.minus(1)
} else {
0
} ?: return@focusChildAtPosition
focusAt(focusablePosition)
}
}
lastPosition = position
}
private fun selectAt(position: Int) {
recycler?.fetchViewAt(position) {
val it = viewSelector(it ?: return@fetchViewAt)
preSelectListener(position)
currentFocus.get()?.isSelected = false
it.isSelected = true
currentFocus = WeakReference(it)
selectListener(position)
}
lastPosition = position
}
fun refocus() {
val itemCount = recycler?.adapter?.itemCount ?: 0
val desiredPosition = if (lastPosition !in 0 until itemCount) 0 else lastPosition
focusAt(desiredPosition)
}
fun reselect() {
val itemCount = recycler?.adapter?.itemCount ?: 0
val desiredPosition = if (lastPosition !in 0 until itemCount) 0 else lastPosition
selectAt(desiredPosition)
}
}
val View.parentView: ViewGroup? get() = parent as? ViewGroup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.