From 55a3094a65fe65fdb5e4492e319578f6446da23c Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 16 Oct 2021 09:09:19 +0700 Subject: [PATCH] Fix AppBar lift state when snapped (#6103) status bar foreground alpha is now handled separately --- .../appbar/HideToolbarOnScrollBehavior.kt | 7 -- .../shape/MaterialShapeDrawableFix.kt | 10 ++ .../tachiyomi/widget/ElevationAppBarLayout.kt | 110 ++++++++++++------ 3 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/com/google/android/material/shape/MaterialShapeDrawableFix.kt diff --git a/app/src/main/java/com/google/android/material/appbar/HideToolbarOnScrollBehavior.kt b/app/src/main/java/com/google/android/material/appbar/HideToolbarOnScrollBehavior.kt index 1d5f01c00..552b391a4 100644 --- a/app/src/main/java/com/google/android/material/appbar/HideToolbarOnScrollBehavior.kt +++ b/app/src/main/java/com/google/android/material/appbar/HideToolbarOnScrollBehavior.kt @@ -5,12 +5,10 @@ import android.view.View import android.view.animation.DecelerateInterpolator import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.animation.doOnEnd import androidx.core.view.ViewCompat import androidx.core.view.marginTop import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.view.findChild -import eu.kanade.tachiyomi.widget.ElevationAppBarLayout import kotlin.math.roundToLong /** @@ -88,11 +86,6 @@ class HideToolbarOnScrollBehavior : AppBarLayout.Behavior() { addUpdateListener { setHeaderTopBottomOffset(coordinatorLayout, child, it.animatedValue as Int) } - doOnEnd { - if ((child as? ElevationAppBarLayout)?.isTransparentWhenNotLifted == true) { - child.isLifted = !isVisible - } - } setIntValues(current, target) start() } diff --git a/app/src/main/java/com/google/android/material/shape/MaterialShapeDrawableFix.kt b/app/src/main/java/com/google/android/material/shape/MaterialShapeDrawableFix.kt new file mode 100644 index 000000000..5abf2ef18 --- /dev/null +++ b/app/src/main/java/com/google/android/material/shape/MaterialShapeDrawableFix.kt @@ -0,0 +1,10 @@ +package com.google.android.material.shape + +/** + * Use this instead of [MaterialShapeDrawable.getAlpha]. + * + * https://github.com/material-components/material-components-android/issues/1796 + */ +fun MaterialShapeDrawable.getStateAlpha(): Int { + return (constantState as? MaterialShapeDrawable.MaterialShapeDrawableState)?.alpha ?: alpha +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt index c1dd79bc9..2a730fc37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.widget +import android.animation.AnimatorSet import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.widget.TextView @@ -12,6 +14,8 @@ import com.google.android.material.animation.AnimationUtils import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.HideToolbarOnScrollBehavior import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.shape.MaterialShapeDrawable +import com.google.android.material.shape.getStateAlpha import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.view.findChild import kotlinx.coroutines.flow.launchIn @@ -25,7 +29,6 @@ class ElevationAppBarLayout @JvmOverloads constructor( ) : AppBarLayout(context, attrs) { private var lifted = true - private var transparent = false private val toolbar by lazy { findViewById(R.id.toolbar) } @@ -42,14 +45,37 @@ class ElevationAppBarLayout @JvmOverloads constructor( field?.alpha = titleTextAlpha } - private var elevationAnimator: ValueAnimator? = null - private var backgroundAlphaAnimator: ValueAnimator? = null + private var animatorSet: AnimatorSet? = null + + private var statusBarForegroundAnimator: ValueAnimator? = null + private val offsetListener = OnOffsetChangedListener { appBarLayout, verticalOffset -> + // Show status bar foreground when offset + val foreground = appBarLayout?.statusBarForeground ?: return@OnOffsetChangedListener + val start = foreground.alpha + val end = if (verticalOffset != 0) 255 else 0 + + statusBarForegroundAnimator?.cancel() + if (animatorSet?.isRunning == true) { + foreground.alpha = end + return@OnOffsetChangedListener + } + if (start != end) { + statusBarForegroundAnimator = ValueAnimator.ofInt(start, end).apply { + duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong() + interpolator = AnimationUtils.LINEAR_INTERPOLATOR + addUpdateListener { + foreground.alpha = it.animatedValue as Int + } + start() + } + } + } var isTransparentWhenNotLifted = false set(value) { if (field != value) { field = value - updateBackgroundAlpha() + updateStates() } } @@ -65,24 +91,7 @@ class ElevationAppBarLayout @JvmOverloads constructor( override fun setLifted(lifted: Boolean): Boolean { return if (this.lifted != lifted) { this.lifted = lifted - val from = elevation - val to = if (lifted) { - resources.getDimension(R.dimen.design_appbar_elevation) - } else { - 0F - } - - elevationAnimator?.cancel() - elevationAnimator = ValueAnimator.ofFloat(from, to).apply { - duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong() - interpolator = AnimationUtils.LINEAR_INTERPOLATOR - addUpdateListener { - elevation = it.animatedValue as Float - } - start() - } - - updateBackgroundAlpha() + updateStates() true } else { false @@ -91,6 +100,9 @@ class ElevationAppBarLayout @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() + addOnOffsetChangedListener(offsetListener) + toolbar.background.alpha = 0 // Use app bar background + titleTextView = toolbar.findChild() findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.let { scope -> toolbar.hierarchyChangeEvents() @@ -112,23 +124,49 @@ class ElevationAppBarLayout @JvmOverloads constructor( } } - private fun updateBackgroundAlpha() { - val newTransparent = if (lifted) false else isTransparentWhenNotLifted - if (transparent != newTransparent) { - transparent = newTransparent - val fromAlpha = if (transparent) 255 else 0 - val toAlpha = if (transparent) 0 else 255 + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + removeOnOffsetChangedListener(offsetListener) + } - backgroundAlphaAnimator?.cancel() - backgroundAlphaAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha).apply { + @SuppressLint("Recycle") + private fun updateStates() { + val animators = mutableListOf() + + val fromElevation = elevation + val toElevation = if (lifted) { + resources.getDimension(R.dimen.design_appbar_elevation) + } else { + 0F + } + if (fromElevation != toElevation) { + ValueAnimator.ofFloat(fromElevation, toElevation).apply { + addUpdateListener { + elevation = it.animatedValue as Float + } + animators.add(this) + } + } + + val transparent = if (lifted) false else isTransparentWhenNotLifted + val fromAlpha = (background as? MaterialShapeDrawable)?.getStateAlpha() ?: background.alpha + val toAlpha = if (transparent) 0 else 255 + if (fromAlpha != toAlpha) { + ValueAnimator.ofInt(fromAlpha, toAlpha).apply { + addUpdateListener { + val value = it.animatedValue as Int + background.alpha = value + } + animators.add(this) + } + } + + if (animators.isNotEmpty()) { + animatorSet?.cancel() + animatorSet = AnimatorSet().apply { duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong() interpolator = AnimationUtils.LINEAR_INTERPOLATOR - addUpdateListener { - val alpha = it.animatedValue as Int - background.alpha = alpha - toolbar?.background?.alpha = alpha - statusBarForeground?.alpha = alpha - } + playTogether(*animators.toTypedArray()) start() } }