diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt index ef42c8ebf..62101f2d4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt @@ -366,18 +366,15 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { ) return } - val translationY = - if (visible) 0F else dip(R.dimen.bottom_nav_height).toFloat() + windowInsets.safeGetBottomInsets() val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED if (mAnimate) { - binding.bottomNavigationView.translateYAnimate(translationY).doOnEnd { - if (visible && bottomSheetBehavior.state != STATE_EXPANDED) { - binding.bottomNavigationView.bringToFront() - } + if (visible) { + binding.bottomNavigationView.bringToFront() + binding.bottomNavigationView.show() + } else { + binding.bottomNavigationView.hide() } } else { - binding.bottomNavigationView.translationY = - translationY binding.bottomNavigationView.isVisible = false if (visible && bottomSheetBehavior.state != STATE_EXPANDED) { binding.bottomNavigationView.bringToFront() diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt index 2c7dc75ed..2ca9bbd37 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt @@ -16,10 +16,13 @@ package code.name.monkey.retromusic.extensions import android.animation.Animator import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.graphics.drawable.BitmapDrawable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver +import android.view.animation.AnimationUtils import android.view.inputmethod.InputMethodManager import android.widget.EditText 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.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil +import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomsheet.BottomSheetBehavior import dev.chrisbanes.insetter.applyInsetter @@ -58,6 +62,84 @@ fun EditText.appHandleColor(): EditText { 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 { return ObjectAnimator.ofFloat(this, "translationY", value) .apply {