Last active
May 24, 2024 00:37
-
-
Save AfzalivE/fdce03eeee8e16203bcc37ba26d7abf3 to your computer and use it in GitHub Desktop.
BottomSheetScrollView for when you have a ViewPager with RecyclerViews in your BottomSheet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.util.AttributeSet | |
import android.view.View | |
import android.widget.FrameLayout | |
import androidx.coordinatorlayout.widget.CoordinatorLayout | |
import androidx.core.view.NestedScrollingChildHelper | |
import androidx.core.view.NestedScrollingParent2 | |
import androidx.core.view.ViewCompat | |
import androidx.recyclerview.widget.LinearLayoutManager | |
import androidx.recyclerview.widget.RecyclerView | |
import com.google.android.material.bottomsheet.BottomSheetBehavior | |
/** | |
* Provides a [NestedScrollView]-like functionality but specifically for the case | |
* when a [BottomSheetBehavior] contains a [ViewPager2] that hosts multiple [RecyclerView] | |
* because in that case, some bug causes the [RecyclerView] to have the same heights and | |
* scroll state. | |
* | |
* See https://issuetracker.google.com/issues/188474850 | |
* | |
* Known issues: | |
* - Unable to scroll up the list if list isn't scrolled to the top but BottomSheet is collapsed | |
* - Overscroll edge not visible | |
*/ | |
class BottomSheetScrollView( | |
context: Context, | |
attrs: AttributeSet?, | |
) : FrameLayout(context, attrs), NestedScrollingParent2 { | |
private val childHelper = NestedScrollingChildHelper(this).apply { | |
isNestedScrollingEnabled = true | |
} | |
private var behavior: BottomSheetBehavior<*>? = null | |
var started = false | |
private var canScroll = false | |
private var pendingCanScroll = false | |
// Used to determine if already reached the top of the list. | |
private var dyPreScroll = 0 | |
init { | |
ViewCompat.setNestedScrollingEnabled(this, true) | |
} | |
private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() { | |
override fun onStateChanged(bottomSheet: View, newState: Int) { | |
onNextScrollStop(newState == BottomSheetBehavior.STATE_EXPANDED) | |
} | |
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit | |
} | |
fun onNextScrollStop(canScroll: Boolean) { | |
pendingCanScroll = canScroll | |
} | |
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean { | |
// ViewPager2's RecyclerView does not participate in this nested scrolling. | |
// This allows it to show its overscroll indicator. | |
if (target is RecyclerView) { | |
val layoutManager = target.layoutManager as LinearLayoutManager | |
if (layoutManager.orientation == LinearLayoutManager.HORIZONTAL) { | |
target.isNestedScrollingEnabled = false | |
} | |
} | |
if (!started) { | |
childHelper.startNestedScroll(axes, type) | |
started = true | |
} | |
return true | |
} | |
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) = Unit | |
override fun onStopNestedScroll(target: View, type: Int) { | |
if (started) { | |
childHelper.stopNestedScroll(type) | |
started = false | |
canScroll = pendingCanScroll | |
} | |
} | |
override fun onNestedScroll( | |
target: View, | |
dxConsumed: Int, | |
dyConsumed: Int, | |
dxUnconsumed: Int, | |
dyUnconsumed: Int, | |
type: Int, | |
) { | |
if (dyUnconsumed == dyPreScroll && dyPreScroll < 0) { | |
canScroll = false | |
} | |
} | |
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { | |
if (!canScroll) { | |
childHelper.dispatchNestedPreScroll(dx, dy, consumed, null, type) | |
// Ensure all dy is consumed to prevent premature scrolling when not allowed. | |
consumed[1] = dy | |
} else { | |
dyPreScroll = dy | |
} | |
} | |
override fun onAttachedToWindow() { | |
super.onAttachedToWindow() | |
behavior = findBottomSheetBehaviorParent(parent as View) as BottomSheetBehavior<*>? | |
behavior?.addBottomSheetCallback(bottomSheetCallback) | |
} | |
override fun onDetachedFromWindow() { | |
super.onDetachedFromWindow() | |
behavior?.removeBottomSheetCallback(bottomSheetCallback) | |
} | |
private fun findBottomSheetBehaviorParent(parent: View): CoordinatorLayout.Behavior<*>? { | |
val params = parent.layoutParams | |
return if (params is CoordinatorLayout.LayoutParams && params.behavior != null) { | |
params.behavior | |
} else { | |
require(parent.parent is View) { | |
"None of this view's ancestors are associated with BottomSheetBehavior" | |
} | |
findBottomSheetBehaviorParent(parent.parent as View) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment