@@ -372,6 +372,47 @@ class RecyclerFastScroller
372372 }
373373 }
374374
375+ override fun onTouchEvent (event : MotionEvent ): Boolean {
376+ return when (event.actionMasked) {
377+ MotionEvent .ACTION_DOWN , MotionEvent .ACTION_MOVE -> {
378+ // Retrieve the adapter to determine item count.
379+ val adapter = recyclerView?.adapter ? : return false
380+
381+ // Force the handle to be selected since the user is touching the track (the parent container) and not the handle itself.
382+ handle.isSelected = true
383+
384+ // The valid scroll area is (height-handle.height), since the position of the handle is defined by it's top edge, we subtract it.
385+ val scrollableHeight = height - handle.height
386+
387+ // Subtract half the handle's height and divide by 2 so the handle centers below the user's finger instead of hanging above or below.
388+ // Divide by the scrollableHeight to make sure that the handle doesn't go off the screen and we use coerceAtLeast to prevent divide by 0 errors
389+ val scrollProportion =
390+ ((event.y - handle.height / 2 ) / scrollableHeight.coerceAtLeast(1 ))
391+ .coerceIn(0f , 1f )
392+
393+ // Calculates the item index we want to go to by multiplying our ScrollProportion to the item count
394+ // e.g. if we are going to 50% then 0.5*itemcount gives us the index we need.
395+ // toInt prevents decimal values, and coerceIn here makes it so when we scroll all the way to the end, we don't get an out of bounds error.
396+ val targetPosition =
397+ (scrollProportion * adapter.itemCount)
398+ .toInt()
399+ .coerceIn(0 , adapter.itemCount - 1 )
400+
401+ try {
402+ recyclerView?.scrollToPosition(targetPosition)
403+ } catch (e: Exception ) {
404+ Timber .w(e, " scrollToPosition" )
405+ }
406+ true
407+ }
408+ MotionEvent .ACTION_UP , MotionEvent .ACTION_CANCEL -> {
409+ handle.isSelected = false
410+ false
411+ }
412+ else -> super .onTouchEvent(event)
413+ }
414+ }
415+
375416 override fun onLayout (
376417 changed : Boolean ,
377418 left : Int ,
0 commit comments