Last active
September 27, 2018 11:53
-
-
Save Ghedeon/333578a3e26dbc37c66687ee8d3273c6 to your computer and use it in GitHub Desktop.
HideBottomViewOnScrollBehavior that ensures BottomView visibility
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.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