Better BottomNavigationView hide/unhide animation

This commit is contained in:
Prathamesh More 2022-05-03 20:31:53 +05:30
parent e7f700ee9a
commit 95e39e4f4c
2 changed files with 87 additions and 8 deletions

View file

@ -366,18 +366,15 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
) )
return return
} }
val translationY =
if (visible) 0F else dip(R.dimen.bottom_nav_height).toFloat() + windowInsets.safeGetBottomInsets()
val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
if (mAnimate) { if (mAnimate) {
binding.bottomNavigationView.translateYAnimate(translationY).doOnEnd { if (visible) {
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) { binding.bottomNavigationView.bringToFront()
binding.bottomNavigationView.bringToFront() binding.bottomNavigationView.show()
} } else {
binding.bottomNavigationView.hide()
} }
} else { } else {
binding.bottomNavigationView.translationY =
translationY
binding.bottomNavigationView.isVisible = false binding.bottomNavigationView.isVisible = false
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) { if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
binding.bottomNavigationView.bringToFront() binding.bottomNavigationView.bringToFront()

View file

@ -16,10 +16,13 @@ package code.name.monkey.retromusic.extensions
import android.animation.Animator import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.animation.AnimationUtils
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.EditText import android.widget.EditText
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
@ -32,6 +35,7 @@ import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
@ -58,6 +62,84 @@ fun EditText.appHandleColor(): EditText {
return this return this
} }
/**
* Potentially animate showing a [BottomNavigationView].
*
* Abruptly changing the visibility leads to a re-layout of main content, animating
* `translationY` leaves a gap where the view was that content does not fill.
*
* Instead, take a snapshot of the view, and animate this in, only changing the visibility (and
* thus layout) when the animation completes.
*/
fun BottomNavigationView.show() {
if (isVisible) return
val parent = parent as ViewGroup
// View needs to be laid out to create a snapshot & know position to animate. If view isn't
// laid out yet, need to do this manually.
if (!isLaidOut) {
measure(
View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.AT_MOST)
)
layout(parent.left, parent.height - measuredHeight, parent.right, parent.height)
}
val drawable = BitmapDrawable(context.resources, drawToBitmap())
drawable.setBounds(left, parent.height, right, parent.height + height)
parent.overlay.add(drawable)
ValueAnimator.ofInt(parent.height, top).apply {
duration = 300
interpolator = AnimationUtils.loadInterpolator(
context,
android.R.interpolator.linear_out_slow_in
)
addUpdateListener {
val newTop = it.animatedValue as Int
drawable.setBounds(left, newTop, right, newTop + height)
}
doOnEnd {
parent.overlay.remove(drawable)
isVisible = true
}
start()
}
}
/**
* Potentially animate hiding a [BottomNavigationView].
*
* Abruptly changing the visibility leads to a re-layout of main content, animating
* `translationY` leaves a gap where the view was that content does not fill.
*
* Instead, take a snapshot, instantly hide the view (so content lays out to fill), then animate
* out the snapshot.
*/
fun BottomNavigationView.hide() {
if (isGone) return
val drawable = BitmapDrawable(context.resources, drawToBitmap())
val parent = parent as ViewGroup
drawable.setBounds(left, top, right, bottom)
parent.overlay.add(drawable)
isGone = true
ValueAnimator.ofInt(top, parent.height).apply {
duration = 300L
interpolator = AnimationUtils.loadInterpolator(
context,
android.R.interpolator.fast_out_linear_in
)
addUpdateListener {
val newTop = it.animatedValue as Int
drawable.setBounds(left, newTop, right, newTop + height)
}
doOnEnd {
parent.overlay.remove(drawable)
}
start()
}
}
fun View.translateYAnimate(value: Float): Animator { fun View.translateYAnimate(value: Float): Animator {
return ObjectAnimator.ofFloat(this, "translationY", value) return ObjectAnimator.ofFloat(this, "translationY", value)
.apply { .apply {