Last active
December 18, 2023 00:04
-
-
Save ValCanBuild/4bbcb47576e875a17440cf2c438e030a to your computer and use it in GitHub Desktop.
Full code of a BottomNavigationBehavior which hides itself on scroll and handles Snackbars, FAB and snapping. https://medium.com/@ValCanBuild/scroll-your-bottom-navigation-view-away-with-10-lines-of-code-346f1ed40e9e
To programmatically do slideUp
and slideDown
just like with the official HideBottomViewOnScrollBehavior
class BottomNavigationBehavior<V : View>(context: Context, attrs: AttributeSet) :
CoordinatorLayout.Behavior<V>(context, attrs) {
@ViewCompat.NestedScrollType
private var lastStartedType: Int = 0
private var offsetAnimator: ValueAnimator? = null
private var isSnappingEnabled = true
@SuppressLint("RestrictedApi")
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
if (dependency is Snackbar.SnackbarLayout) {
updateSnackbar(child, dependency)
}
return super.layoutDependsOn(parent, child, dependency)
}
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
if (axes != ViewCompat.SCROLL_AXIS_VERTICAL)
return false
lastStartedType = type
offsetAnimator?.cancel()
return true
}
override fun onNestedPreScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
target: View,
dx: Int,
dy: Int,
consumed: IntArray,
type: Int
) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
child.translationY = max(0f, min(child.height.toFloat(), child.translationY + dy))
}
override fun onStopNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: V,
target: View,
type: Int
) {
if (!isSnappingEnabled) {
return
}
// Add snap behaviour
// Logic here borrowed from AppBarLayout onStopNestedScroll
if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
// find nearest seam
val currTranslation = child.translationY
val childHalfHeight = child.height * 0.5f
// translate down
if (currTranslation >= childHalfHeight) {
animateBarVisibility(child, isVisible = false)
}
// translate up
else {
animateBarVisibility(child, isVisible = true)
}
}
}
private fun animateBarVisibility(child: View, isVisible: Boolean) {
if (offsetAnimator == null) {
offsetAnimator = ValueAnimator().apply {
interpolator = DecelerateInterpolator()
duration = 150L
}
offsetAnimator?.addUpdateListener {
child.translationY = it.animatedValue as Float
}
} else {
offsetAnimator?.cancel()
}
val targetTranslation = if (isVisible) 0f else child.height.toFloat()
offsetAnimator?.setFloatValues(child.translationY, targetTranslation)
offsetAnimator?.start()
}
private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
if (snackbarLayout.layoutParams is CoordinatorLayout.LayoutParams) {
// Remove messy shadow
snackbarLayout.outlineProvider = null
val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams
params.anchorId = child.id
params.anchorGravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
}
}
fun slideUp(child: BottomNavigationView) {
child.visibility = View.VISIBLE
animateBarVisibility(child, isVisible = true)
}
fun slideDown(child: BottomNavigationView, isGone: Boolean) {
animateBarVisibility(child, isVisible = false)
if (isGone) {
child.visibility = View.GONE
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey sorry, I'm not actively doing android development and the moment so I can't help out - I'd see if there's an equivalent way to do as recommended by the library