Skip to content

Instantly share code, notes, and snippets.

@Ghedeon
Last active September 27, 2018 11:53
Show Gist options
  • Save Ghedeon/333578a3e26dbc37c66687ee8d3273c6 to your computer and use it in GitHub Desktop.
Save Ghedeon/333578a3e26dbc37c66687ee8d3273c6 to your computer and use it in GitHub Desktop.
HideBottomViewOnScrollBehavior that ensures BottomView visibility
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.content.Context
import android.support.design.animation.AnimationUtils
import android.support.design.widget.CoordinatorLayout
import android.support.design.widget.Snackbar
import android.support.v4.view.ViewCompat
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.view.ViewPropertyAnimator
import android.view.ViewTreeObserver.OnGlobalLayoutListener
/**
* @see <a href="https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/behavior/HideBottomViewOnScrollBehavior.java">HideBottomViewOnScrollBehavior.java</a>
*/
class HideBottomViewOnScrollBehavior<V : View>(context: Context, attrs: AttributeSet) :
CoordinatorLayout.Behavior<V>(context, attrs) {
private var height: Int = 0
private var currentState = STATE_SCROLLED_UP
private var currentAnimator: ViewPropertyAnimator? = null
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
if (dependency is Snackbar.SnackbarLayout) {
updateSnackbar(child, dependency)
}
return super.layoutDependsOn(parent, child, dependency)
}
private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
val params = snackbarLayout.layoutParams as? CoordinatorLayout.LayoutParams
params?.run {
anchorId = child.id
anchorGravity = Gravity.TOP
gravity = Gravity.TOP
snackbarLayout.layoutParams = params
}
}
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
super.onStopNestedScroll(coordinatorLayout, child, target, type)
assureBottomViewVisibility(target, child)
}
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
height = child.measuredHeight
return super.onLayoutChild(parent, child, layoutDirection)
}
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View,
target: View, axes: Int, type: Int): Boolean =
axes == ViewCompat.SCROLL_AXIS_VERTICAL
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: V,
target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
@ViewCompat.NestedScrollType type: Int) {
if (currentState != STATE_SCROLLED_DOWN && dyConsumed > 0) {
slideDown(child)
} else if (currentState != STATE_SCROLLED_UP && dyConsumed < 0) {
slideUp(child)
}
}
private fun slideUp(child: V) {
if (currentAnimator != null) {
currentAnimator?.cancel()
child.clearAnimation()
}
currentState = STATE_SCROLLED_UP
animateChildTo(child, 0, ENTER_ANIMATION_DURATION, AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR)
}
private fun slideDown(child: V) {
if (currentAnimator != null) {
currentAnimator?.cancel()
child.clearAnimation()
}
currentState = STATE_SCROLLED_DOWN
animateChildTo(child, height, EXIT_ANIMATION_DURATION, AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
}
private fun animateChildTo(child: V, targetY: Int, duration: Long, interpolator: TimeInterpolator) {
currentAnimator = child.animate()
.translationY(targetY.toFloat())
.setInterpolator(interpolator)
.setDuration(duration)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
currentAnimator = null
}
})
}
/**
* Edge case when expandable RecyclerView is collapsed and can't be scrolled
*/
private fun assureBottomViewVisibility(target: View, child: V) {
val recycler = target as? RecyclerView
recycler?.viewTreeObserver?.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
recycler.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (!recycler.canScrollVertically(DOWN) && !recycler.canScrollVertically(UP)) {
slideUp(child)
}
}
})
}
}
private const val ENTER_ANIMATION_DURATION = 225L
private const val EXIT_ANIMATION_DURATION = 175L
private const val STATE_SCROLLED_DOWN = 1
private const val STATE_SCROLLED_UP = 2
private const val DOWN = 1
private const val UP = -1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment