105 lines
4.0 KiB
Kotlin
105 lines
4.0 KiB
Kotlin
package com.gh.common.view
|
|
|
|
import android.content.Context
|
|
import android.os.Build
|
|
import androidx.core.view.NestedScrollingParent
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import android.util.AttributeSet
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
|
|
open class NestedRecyclerView : androidx.recyclerview.widget.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
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= 21) {
|
|
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
|
|
}
|
|
} |