Fix AppBar lift state when snapped (#6103)
status bar foreground alpha is now handled separately
This commit is contained in:
parent
b4490e209b
commit
55a3094a65
@ -5,12 +5,10 @@ import android.view.View
|
|||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.animation.doOnEnd
|
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.marginTop
|
import androidx.core.view.marginTop
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
import eu.kanade.tachiyomi.util.view.findChild
|
import eu.kanade.tachiyomi.util.view.findChild
|
||||||
import eu.kanade.tachiyomi.widget.ElevationAppBarLayout
|
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,11 +86,6 @@ class HideToolbarOnScrollBehavior : AppBarLayout.Behavior() {
|
|||||||
addUpdateListener {
|
addUpdateListener {
|
||||||
setHeaderTopBottomOffset(coordinatorLayout, child, it.animatedValue as Int)
|
setHeaderTopBottomOffset(coordinatorLayout, child, it.animatedValue as Int)
|
||||||
}
|
}
|
||||||
doOnEnd {
|
|
||||||
if ((child as? ElevationAppBarLayout)?.isTransparentWhenNotLifted == true) {
|
|
||||||
child.isLifted = !isVisible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setIntValues(current, target)
|
setIntValues(current, target)
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.animation.AnimatorSet
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.TextView
|
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.AppBarLayout
|
||||||
import com.google.android.material.appbar.HideToolbarOnScrollBehavior
|
import com.google.android.material.appbar.HideToolbarOnScrollBehavior
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
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.R
|
||||||
import eu.kanade.tachiyomi.util.view.findChild
|
import eu.kanade.tachiyomi.util.view.findChild
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -25,7 +29,6 @@ class ElevationAppBarLayout @JvmOverloads constructor(
|
|||||||
) : AppBarLayout(context, attrs) {
|
) : AppBarLayout(context, attrs) {
|
||||||
|
|
||||||
private var lifted = true
|
private var lifted = true
|
||||||
private var transparent = false
|
|
||||||
|
|
||||||
private val toolbar by lazy { findViewById<MaterialToolbar>(R.id.toolbar) }
|
private val toolbar by lazy { findViewById<MaterialToolbar>(R.id.toolbar) }
|
||||||
|
|
||||||
@ -42,14 +45,37 @@ class ElevationAppBarLayout @JvmOverloads constructor(
|
|||||||
field?.alpha = titleTextAlpha
|
field?.alpha = titleTextAlpha
|
||||||
}
|
}
|
||||||
|
|
||||||
private var elevationAnimator: ValueAnimator? = null
|
private var animatorSet: AnimatorSet? = null
|
||||||
private var backgroundAlphaAnimator: ValueAnimator? = 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
|
var isTransparentWhenNotLifted = false
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field != value) {
|
if (field != value) {
|
||||||
field = value
|
field = value
|
||||||
updateBackgroundAlpha()
|
updateStates()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,24 +91,7 @@ class ElevationAppBarLayout @JvmOverloads constructor(
|
|||||||
override fun setLifted(lifted: Boolean): Boolean {
|
override fun setLifted(lifted: Boolean): Boolean {
|
||||||
return if (this.lifted != lifted) {
|
return if (this.lifted != lifted) {
|
||||||
this.lifted = lifted
|
this.lifted = lifted
|
||||||
val from = elevation
|
updateStates()
|
||||||
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()
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -91,6 +100,9 @@ class ElevationAppBarLayout @JvmOverloads constructor(
|
|||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
super.onAttachedToWindow()
|
super.onAttachedToWindow()
|
||||||
|
addOnOffsetChangedListener(offsetListener)
|
||||||
|
toolbar.background.alpha = 0 // Use app bar background
|
||||||
|
|
||||||
titleTextView = toolbar.findChild<TextView>()
|
titleTextView = toolbar.findChild<TextView>()
|
||||||
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.let { scope ->
|
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.let { scope ->
|
||||||
toolbar.hierarchyChangeEvents()
|
toolbar.hierarchyChangeEvents()
|
||||||
@ -112,23 +124,49 @@ class ElevationAppBarLayout @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateBackgroundAlpha() {
|
override fun onDetachedFromWindow() {
|
||||||
val newTransparent = if (lifted) false else isTransparentWhenNotLifted
|
super.onDetachedFromWindow()
|
||||||
if (transparent != newTransparent) {
|
removeOnOffsetChangedListener(offsetListener)
|
||||||
transparent = newTransparent
|
}
|
||||||
val fromAlpha = if (transparent) 255 else 0
|
|
||||||
val toAlpha = if (transparent) 0 else 255
|
|
||||||
|
|
||||||
backgroundAlphaAnimator?.cancel()
|
@SuppressLint("Recycle")
|
||||||
backgroundAlphaAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha).apply {
|
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 {
|
||||||
|
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()
|
duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong()
|
||||||
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
|
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
|
||||||
addUpdateListener {
|
playTogether(*animators.toTypedArray())
|
||||||
val alpha = it.animatedValue as Int
|
|
||||||
background.alpha = alpha
|
|
||||||
toolbar?.background?.alpha = alpha
|
|
||||||
statusBarForeground?.alpha = alpha
|
|
||||||
}
|
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user