Skip to content

Instantly share code, notes, and snippets.

@cbeyls
Last active February 8, 2024 19:17
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cbeyls/e5e16bde73bb10486dfd1fc101b1b11a to your computer and use it in GitHub Desktop.
Save cbeyls/e5e16bde73bb10486dfd1fc101b1b11a to your computer and use it in GitHub Desktop.
Extension function to enforce a single scroll direction for a RecyclerView
package be.digitalia.samples.utils
import android.view.MotionEvent
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener
import kotlin.math.abs
fun RecyclerView.enforceSingleScrollDirection() {
val enforcer = SingleScrollDirectionEnforcer()
addOnItemTouchListener(enforcer)
addOnScrollListener(enforcer)
}
private class SingleScrollDirectionEnforcer : RecyclerView.OnScrollListener(), OnItemTouchListener {
private var scrollState = RecyclerView.SCROLL_STATE_IDLE
private var scrollPointerId = -1
private var initialTouchX = 0
private var initialTouchY = 0
private var dx = 0
private var dy = 0
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.actionMasked) {
MotionEvent.ACTION_DOWN -> {
scrollPointerId = e.getPointerId(0)
initialTouchX = (e.x + 0.5f).toInt()
initialTouchY = (e.y + 0.5f).toInt()
}
MotionEvent.ACTION_POINTER_DOWN -> {
val actionIndex = e.actionIndex
scrollPointerId = e.getPointerId(actionIndex)
initialTouchX = (e.getX(actionIndex) + 0.5f).toInt()
initialTouchY = (e.getY(actionIndex) + 0.5f).toInt()
}
MotionEvent.ACTION_MOVE -> {
val index = e.findPointerIndex(scrollPointerId)
if (index >= 0 && scrollState != RecyclerView.SCROLL_STATE_DRAGGING) {
val x = (e.getX(index) + 0.5f).toInt()
val y = (e.getY(index) + 0.5f).toInt()
dx = x - initialTouchX
dy = y - initialTouchY
}
}
}
return false
}
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
val oldState = scrollState
scrollState = newState
if (oldState == RecyclerView.SCROLL_STATE_IDLE && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
recyclerView.layoutManager?.let { layoutManager ->
val canScrollHorizontally = layoutManager.canScrollHorizontally()
val canScrollVertically = layoutManager.canScrollVertically()
if (canScrollHorizontally != canScrollVertically) {
if ((canScrollHorizontally && abs(dy) > abs(dx))
|| (canScrollVertically && abs(dx) > abs(dy))) {
recyclerView.stopScroll()
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment