Skip to content

Instantly share code, notes, and snippets.

@fluidsonic
Last active October 21, 2023 09:32
Show Gist options
  • Select an option

  • Save fluidsonic/c3ec427cb204a87813f2252941e9b822 to your computer and use it in GitHub Desktop.

Select an option

Save fluidsonic/c3ec427cb204a87813f2252941e9b822 to your computer and use it in GitHub Desktop.
A RecyclerView subclass for Android which makes descendent NestedScrollViews scrollable
package com.github.fluidsonic.nestedrecyclerview
import android.content.Context
import android.support.v4.view.NestedScrollingParent
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
open class NestedRecyclerView : RecyclerView, NestedScrollingParent {
private var nestedScrollTarget: View? = null
private var nestedScrollTargetIsBeingDragged = false
private var nestedScrollTargetWasUnableToScroll = false
private var skipsTouchInterception = false
constructor(context: Context) :
super(context)
constructor(context: Context, attrs: AttributeSet?) :
super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
val temporarilySkipsInterception = nestedScrollTarget != null
if (temporarilySkipsInterception) {
// If a descendent view is scrolling we set a flag to temporarily skip our onInterceptTouchEvent implementation
skipsTouchInterception = true
}
// First dispatch, potentially skipping our onInterceptTouchEvent
var handled = super.dispatchTouchEvent(ev)
if (temporarilySkipsInterception) {
skipsTouchInterception = false
// If the first dispatch yielded no result or we noticed that the descendent view is unable to scroll in the
// direction the user is scrolling, we dispatch once more but without skipping our onInterceptTouchEvent.
// Note that RecyclerView automatically cancels active touches of all its descendents once it starts scrolling
// so we don't have to do that.
if (!handled || nestedScrollTargetWasUnableToScroll) {
handled = super.dispatchTouchEvent(ev)
}
}
return handled
}
// Skips RecyclerView's onInterceptTouchEvent if requested
override fun onInterceptTouchEvent(e: MotionEvent) =
!skipsTouchInterception && super.onInterceptTouchEvent(e)
override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) {
if (target === nestedScrollTarget && !nestedScrollTargetIsBeingDragged) {
if (dyConsumed != 0) {
// The descendent was actually scrolled, so we won't bother it any longer.
// It will receive all future events until it finished scrolling.
nestedScrollTargetIsBeingDragged = true
nestedScrollTargetWasUnableToScroll = false
}
else if (dyConsumed == 0 && dyUnconsumed != 0) {
// The descendent tried scrolling in response to touch movements but was not able to do so.
// We remember that in order to allow RecyclerView to take over scrolling.
nestedScrollTargetWasUnableToScroll = true
target.parent?.requestDisallowInterceptTouchEvent(false)
}
}
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int) {
if (axes and View.SCROLL_AXIS_VERTICAL != 0) {
// A descendent started scrolling, so we'll observe it.
nestedScrollTarget = target
nestedScrollTargetIsBeingDragged = false
nestedScrollTargetWasUnableToScroll = false
}
super.onNestedScrollAccepted(child, target, axes)
}
// We only support vertical scrolling.
override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int) =
(nestedScrollAxes and View.SCROLL_AXIS_VERTICAL != 0)
override fun onStopNestedScroll(child: View) {
// The descendent finished scrolling. Clean up!
nestedScrollTarget = null
nestedScrollTargetIsBeingDragged = false
nestedScrollTargetWasUnableToScroll = false
}
}
@mvn-vinhhuynh-dn
Copy link

onNestedScrollAccepted just added in API 21 and higher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment