Browse Source

Fix AppBar lift state when snapped (#6103)

status bar foreground alpha is now handled separately
Ivan Iskandar 3 years ago
parent
commit
55a3094a65

+ 0 - 7
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()
         }

+ 10 - 0
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
+}

+ 74 - 36
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<MaterialToolbar>(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<TextView>()
         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 {
-                duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong()
-                interpolator = AnimationUtils.LINEAR_INTERPOLATOR
+    @SuppressLint("Recycle")
+    private fun updateStates() {
+        val animators = mutableListOf<ValueAnimator>()
+
+        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 {
-                    val alpha = it.animatedValue as Int
-                    background.alpha = alpha
-                    toolbar?.background?.alpha = alpha
-                    statusBarForeground?.alpha = alpha
+                    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
+                playTogether(*animators.toTypedArray())
                 start()
             }
         }