ui: start moving to pre-packaged anims
This commit is contained in:
parent
50829a54d3
commit
22ce9988c8
6 changed files with 155 additions and 154 deletions
|
@ -115,8 +115,8 @@ abstract class DetailFragment<P : MusicParent, C : Music> :
|
||||||
|
|
||||||
val outRatio = min(ratio * 2, 1f)
|
val outRatio = min(ratio * 2, 1f)
|
||||||
val detailHeader = binding.detailHeader
|
val detailHeader = binding.detailHeader
|
||||||
detailHeader.scaleX = 1 - 0.05f * outRatio
|
detailHeader.scaleX = 1 - 0.2f * outRatio / (5f / 3f)
|
||||||
detailHeader.scaleY = 1 - 0.05f * outRatio
|
detailHeader.scaleY = 1 - 0.2f * outRatio / (5f / 3f)
|
||||||
detailHeader.alpha = 1 - outRatio
|
detailHeader.alpha = 1 - outRatio
|
||||||
|
|
||||||
val inRatio = max(ratio - 0.5f, 0f) * 2
|
val inRatio = max(ratio - 0.5f, 0f) * 2
|
||||||
|
|
|
@ -46,7 +46,7 @@ import com.leinardi.android.speeddial.SpeedDialView
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.StationaryAnim
|
import org.oxycblt.auxio.ui.AnimConfig
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
import org.oxycblt.auxio.util.getDimen
|
import org.oxycblt.auxio.util.getDimen
|
||||||
import org.oxycblt.auxio.util.getDimenPixels
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
|
@ -78,7 +78,7 @@ class ThemedSpeedDialView : SpeedDialView {
|
||||||
@AttrRes defStyleAttr: Int
|
@AttrRes defStyleAttr: Int
|
||||||
) : super(context, attrs, defStyleAttr)
|
) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
private val inAnim = StationaryAnim.forMediumComponent(context)
|
private val stationaryConfig = AnimConfig.of(context, AnimConfig.STANDARD, AnimConfig.MEDIUM2)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Work around ripple bug on Android 12 when useCompatPadding = true.
|
// Work around ripple bug on Android 12 when useCompatPadding = true.
|
||||||
|
@ -142,7 +142,7 @@ class ThemedSpeedDialView : SpeedDialView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMainFabAnimator(isOpen: Boolean): Animator {
|
private fun createMainFabAnimator(isOpen: Boolean): Animator {
|
||||||
val totalDuration = inAnim.duration
|
val totalDuration = stationaryConfig.duration
|
||||||
val partialDuration = totalDuration / 2 // This is half of the total duration
|
val partialDuration = totalDuration / 2 // This is half of the total duration
|
||||||
val delay = totalDuration / 4 // This is one fourth of the total duration
|
val delay = totalDuration / 4 // This is one fourth of the total duration
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ class ThemedSpeedDialView : SpeedDialView {
|
||||||
val animatorSet =
|
val animatorSet =
|
||||||
AnimatorSet().apply {
|
AnimatorSet().apply {
|
||||||
playTogether(backgroundTintAnimator, imageTintAnimator, levelAnimator)
|
playTogether(backgroundTintAnimator, imageTintAnimator, levelAnimator)
|
||||||
interpolator = inAnim.interpolator
|
interpolator = stationaryConfig.interpolator
|
||||||
}
|
}
|
||||||
animatorSet.start()
|
animatorSet.start()
|
||||||
return animatorSet
|
return animatorSet
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.home.fastscroll
|
package org.oxycblt.auxio.home.fastscroll
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
|
@ -37,8 +38,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.list.recycler.AuxioRecyclerView
|
import org.oxycblt.auxio.list.recycler.AuxioRecyclerView
|
||||||
import org.oxycblt.auxio.ui.InAnim
|
import org.oxycblt.auxio.ui.MaterialFader
|
||||||
import org.oxycblt.auxio.ui.OutAnim
|
|
||||||
import org.oxycblt.auxio.util.getDimenPixels
|
import org.oxycblt.auxio.util.getDimenPixels
|
||||||
import org.oxycblt.auxio.util.getDrawableCompat
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
import org.oxycblt.auxio.util.isRtl
|
import org.oxycblt.auxio.util.isRtl
|
||||||
|
@ -84,9 +84,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
background = context.getDrawableCompat(R.drawable.ui_scroll_thumb)
|
background = context.getDrawableCompat(R.drawable.ui_scroll_thumb)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val thumbEnter = InAnim.forSmallComponent(context)
|
|
||||||
private val thumbExit = OutAnim.forSmallComponent(context)
|
|
||||||
|
|
||||||
private val thumbWidth = thumbView.background.intrinsicWidth
|
private val thumbWidth = thumbView.background.intrinsicWidth
|
||||||
private val thumbHeight = thumbView.background.intrinsicHeight
|
private val thumbHeight = thumbView.background.intrinsicHeight
|
||||||
private val thumbPadding = Rect(0, 0, 0, 0)
|
private val thumbPadding = Rect(0, 0, 0, 0)
|
||||||
|
@ -114,8 +111,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val popupEnter = InAnim.forSmallComponent(context)
|
private val fader = MaterialFader.forSmallComponent(context)
|
||||||
private val popupExit = OutAnim.forSmallComponent(context)
|
private var thumbAnimator: Animator? = null
|
||||||
|
private var popupAnimator: Animator? = null
|
||||||
|
|
||||||
private var showingPopup = false
|
private var showingPopup = false
|
||||||
|
|
||||||
|
@ -426,12 +424,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
showingThumb = true
|
showingThumb = true
|
||||||
thumbView
|
thumbAnimator?.cancel()
|
||||||
.animate()
|
thumbAnimator = fader.fadeIn(thumbView).also { it.start() }
|
||||||
.scaleX(1f)
|
|
||||||
.setInterpolator(thumbEnter.interpolator)
|
|
||||||
.setDuration(thumbEnter.duration)
|
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideScrollbar() {
|
private fun hideScrollbar() {
|
||||||
|
@ -440,12 +434,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
showingThumb = false
|
showingThumb = false
|
||||||
thumbView
|
thumbAnimator?.cancel()
|
||||||
.animate()
|
thumbAnimator = fader.fadeOut(thumbView).also { it.start() }
|
||||||
.scaleX(0f)
|
|
||||||
.setInterpolator(thumbExit.interpolator)
|
|
||||||
.setDuration(thumbExit.duration)
|
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showPopup() {
|
private fun showPopup() {
|
||||||
|
@ -458,13 +448,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
popupView.alpha = 1f
|
popupView.alpha = 1f
|
||||||
showingPopup = true
|
showingPopup = true
|
||||||
popupView
|
popupAnimator?.cancel()
|
||||||
.animate()
|
popupAnimator = fader.fadeIn(popupView).also { it.start() }
|
||||||
.scaleX(1f)
|
|
||||||
.scaleY(1f)
|
|
||||||
.setInterpolator(popupEnter.interpolator)
|
|
||||||
.setDuration(popupEnter.duration)
|
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hidePopup() {
|
private fun hidePopup() {
|
||||||
|
@ -473,14 +458,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
showingPopup = false
|
showingPopup = false
|
||||||
popupView
|
popupAnimator?.cancel()
|
||||||
.animate()
|
popupAnimator = fader.fadeOut(popupView).also { it.start() }
|
||||||
.alpha(0f)
|
|
||||||
.scaleX(0.75f)
|
|
||||||
.scaleY(0.75f)
|
|
||||||
.setInterpolator(popupExit.interpolator)
|
|
||||||
.setDuration(popupExit.duration)
|
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- LAYOUT STATE ---
|
// --- LAYOUT STATE ---
|
||||||
|
|
|
@ -18,12 +18,12 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.ui
|
package org.oxycblt.auxio.playback.ui
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
import android.animation.Animator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import org.oxycblt.auxio.ui.MaterialCornerAnim
|
||||||
import org.oxycblt.auxio.ui.RippleFixMaterialButton
|
import org.oxycblt.auxio.ui.RippleFixMaterialButton
|
||||||
import org.oxycblt.auxio.ui.StationaryAnim
|
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,9 +43,8 @@ class AnimatedMaterialButton : RippleFixMaterialButton {
|
||||||
defStyleAttr: Int
|
defStyleAttr: Int
|
||||||
) : super(context, attrs, defStyleAttr)
|
) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
private var currentCornerRadiusRatio = 0f
|
private var animator: Animator? = null
|
||||||
private var animator: ValueAnimator? = null
|
private val anim = MaterialCornerAnim(context)
|
||||||
private val anim = StationaryAnim.forMediumComponent(context)
|
|
||||||
|
|
||||||
override fun setActivated(activated: Boolean) {
|
override fun setActivated(activated: Boolean) {
|
||||||
super.setActivated(activated)
|
super.setActivated(activated)
|
||||||
|
@ -55,22 +54,12 @@ class AnimatedMaterialButton : RippleFixMaterialButton {
|
||||||
if (!isLaidOut) {
|
if (!isLaidOut) {
|
||||||
// Not laid out, initialize it without animation before drawing.
|
// Not laid out, initialize it without animation before drawing.
|
||||||
L.d("Not laid out, immediately updating corner radius")
|
L.d("Not laid out, immediately updating corner radius")
|
||||||
updateCornerRadiusRatio(targetRadius)
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize { it.width() * targetRadius }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
L.d("Starting corner radius animation")
|
L.d("Starting corner radius animation")
|
||||||
animator?.cancel()
|
animator?.cancel()
|
||||||
animator =
|
animator = anim.animate(this, width * targetRadius).also { it.start() }
|
||||||
anim
|
|
||||||
.genericFloat(currentCornerRadiusRatio, targetRadius, 0, ::updateCornerRadiusRatio)
|
|
||||||
.also { it.start() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateCornerRadiusRatio(ratio: Float) {
|
|
||||||
currentCornerRadiusRatio = ratio
|
|
||||||
// Can't reproduce the intrinsic ratio corner radius, just manually implement it with
|
|
||||||
// a dimension value.
|
|
||||||
shapeAppearanceModel = shapeAppearanceModel.withCornerSize { it.width() * ratio }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,58 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorSet
|
||||||
import android.animation.TimeInterpolator
|
import android.animation.TimeInterpolator
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.core.graphics.toRectF
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import com.google.android.material.R as MR
|
import com.google.android.material.R as MR
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.motion.MotionUtils
|
import com.google.android.material.motion.MotionUtils
|
||||||
|
|
||||||
data class Anim(val interpolator: TimeInterpolator, val duration: Long) {
|
class AnimConfig(
|
||||||
|
context: Context,
|
||||||
|
@AttrRes interpolatorRes: Int,
|
||||||
|
@AttrRes durationRes: Int,
|
||||||
|
defaultDuration: Int
|
||||||
|
) {
|
||||||
|
val interpolator: TimeInterpolator =
|
||||||
|
MotionUtils.resolveThemeInterpolator(context, interpolatorRes, FastOutSlowInInterpolator())
|
||||||
|
val duration: Long =
|
||||||
|
MotionUtils.resolveThemeDuration(context, durationRes, defaultDuration).toLong()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val STANDARD = MR.attr.motionEasingStandardInterpolator
|
||||||
|
val EMPHASIZED = MR.attr.motionEasingEmphasizedInterpolator
|
||||||
|
val EMPHASIZED_ACCELERATE = MR.attr.motionEasingEmphasizedAccelerateInterpolator
|
||||||
|
val EMPHASIZED_DECELERATE = MR.attr.motionEasingEmphasizedDecelerateInterpolator
|
||||||
|
val SHORT1 = MR.attr.motionDurationShort1 to 50
|
||||||
|
val SHORT2 = MR.attr.motionDurationShort2 to 100
|
||||||
|
val SHORT3 = MR.attr.motionDurationShort3 to 150
|
||||||
|
val SHORT4 = MR.attr.motionDurationShort4 to 200
|
||||||
|
val MEDIUM1 = MR.attr.motionDurationMedium1 to 250
|
||||||
|
val MEDIUM2 = MR.attr.motionDurationMedium2 to 300
|
||||||
|
val MEDIUM3 = MR.attr.motionDurationMedium3 to 350
|
||||||
|
val MEDIUM4 = MR.attr.motionDurationMedium4 to 400
|
||||||
|
val LONG1 = MR.attr.motionDurationLong1 to 450
|
||||||
|
val LONG2 = MR.attr.motionDurationLong2 to 500
|
||||||
|
val LONG3 = MR.attr.motionDurationLong3 to 550
|
||||||
|
val LONG4 = MR.attr.motionDurationLong4 to 600
|
||||||
|
val EXTRA_LONG1 = MR.attr.motionDurationExtraLong1 to 700
|
||||||
|
val EXTRA_LONG2 = MR.attr.motionDurationExtraLong2 to 800
|
||||||
|
val EXTRA_LONG3 = MR.attr.motionDurationExtraLong3 to 900
|
||||||
|
val EXTRA_LONG4 = MR.attr.motionDurationExtraLong4 to 1000
|
||||||
|
|
||||||
|
fun of(context: Context, @AttrRes interpolator: Int, duration: Pair<Int, Int>) =
|
||||||
|
AnimConfig(context, interpolator, duration.first, duration.second)
|
||||||
|
}
|
||||||
|
|
||||||
inline fun genericFloat(
|
inline fun genericFloat(
|
||||||
from: Float,
|
from: Float,
|
||||||
to: Float,
|
to: Float,
|
||||||
|
@ -34,52 +78,94 @@ data class Anim(val interpolator: TimeInterpolator, val duration: Long) {
|
||||||
): ValueAnimator =
|
): ValueAnimator =
|
||||||
ValueAnimator.ofFloat(from, to).apply {
|
ValueAnimator.ofFloat(from, to).apply {
|
||||||
startDelay = delayMs
|
startDelay = delayMs
|
||||||
duration = this@Anim.duration
|
duration = this@AnimConfig.duration
|
||||||
interpolator = this@Anim.interpolator
|
interpolator = this@AnimConfig.interpolator
|
||||||
addUpdateListener { update(animatedValue as Float) }
|
addUpdateListener { update(animatedValue as Float) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object StationaryAnim {
|
class MaterialCornerAnim(context: Context) {
|
||||||
fun forMediumComponent(context: Context) =
|
private val config = AnimConfig.of(context, AnimConfig.STANDARD, AnimConfig.MEDIUM2)
|
||||||
Anim(
|
|
||||||
MotionUtils.resolveThemeInterpolator(
|
fun animate(button: MaterialButton, sizeDp: Float): Animator {
|
||||||
context, MR.attr.motionEasingStandardInterpolator, FastOutSlowInInterpolator()),
|
val shapeModel = button.shapeAppearanceModel
|
||||||
MotionUtils.resolveThemeDuration(context, MR.attr.motionDurationMedium2, 300).toLong())
|
val bounds = Rect(0, 0, button.width, button.height)
|
||||||
|
val start = shapeModel.topRightCornerSize.getCornerSize(bounds.toRectF())
|
||||||
|
return config.genericFloat(start, sizeDp) { size ->
|
||||||
|
button.shapeAppearanceModel = shapeModel.withCornerSize { size }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object InAnim {
|
class MaterialFader private constructor(context: Context, private val scale: Float) {
|
||||||
fun forSmallComponent(context: Context) =
|
private val alphaOutConfig =
|
||||||
Anim(
|
AnimConfig.of(context, AnimConfig.EMPHASIZED_ACCELERATE, AnimConfig.SHORT3)
|
||||||
MotionUtils.resolveThemeInterpolator(
|
private val scaleOutConfig =
|
||||||
context,
|
AnimConfig.of(context, AnimConfig.EMPHASIZED_ACCELERATE, AnimConfig.MEDIUM1)
|
||||||
MR.attr.motionEasingStandardDecelerateInterpolator,
|
private val inConfig = AnimConfig.of(context, AnimConfig.EMPHASIZED, AnimConfig.LONG2)
|
||||||
FastOutSlowInInterpolator()),
|
|
||||||
MotionUtils.resolveThemeDuration(context, MR.attr.motionDurationMedium1, 300).toLong())
|
|
||||||
|
|
||||||
fun forMediumComponent(context: Context) =
|
fun jumpToFadeOut(view: View) {
|
||||||
Anim(
|
view.apply {
|
||||||
MotionUtils.resolveThemeInterpolator(
|
alpha = 0f
|
||||||
context,
|
scaleX = scale
|
||||||
MR.attr.motionEasingEmphasizedDecelerateInterpolator,
|
scaleY = scale
|
||||||
FastOutSlowInInterpolator()),
|
isInvisible = true
|
||||||
MotionUtils.resolveThemeDuration(context, MR.attr.motionDurationMedium2, 300).toLong())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jumpToFadeIn(view: View) {
|
||||||
|
view.apply {
|
||||||
|
alpha = 1f
|
||||||
|
scaleX = 1.0f
|
||||||
|
scaleY = 1.0f
|
||||||
|
isInvisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fadeOut(view: View): Animator {
|
||||||
|
if (!view.isLaidOut) {
|
||||||
|
jumpToFadeOut(view)
|
||||||
|
return AnimatorSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
val alphaAnimator = alphaOutConfig.genericFloat(view.alpha, 0f) { view.alpha = it }
|
||||||
|
val scaleXAnimator = scaleOutConfig.genericFloat(view.scaleX, scale) { view.scaleX = it }
|
||||||
|
val scaleYAnimator = scaleOutConfig.genericFloat(view.scaleY, scale) { view.scaleY = it }
|
||||||
|
return AnimatorSet().apply { playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fadeIn(view: View): Animator {
|
||||||
|
if (!view.isLaidOut) {
|
||||||
|
jumpToFadeIn(view)
|
||||||
|
return AnimatorSet()
|
||||||
|
}
|
||||||
|
val alphaAnimator =
|
||||||
|
inConfig.genericFloat(view.alpha, 1f) {
|
||||||
|
view.alpha = it
|
||||||
|
view.isInvisible = view.alpha == 0f
|
||||||
|
}
|
||||||
|
val scaleXAnimator = inConfig.genericFloat(view.scaleX, 1.0f) { view.scaleX = it }
|
||||||
|
val scaleYAnimator = inConfig.genericFloat(view.scaleY, 1.0f) { view.scaleY = it }
|
||||||
|
return AnimatorSet().apply { playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun forSmallComponent(context: Context) = MaterialFader(context, 0.4f)
|
||||||
|
|
||||||
|
fun forLargeComponent(context: Context) = MaterialFader(context, 0.9f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object OutAnim {
|
class MaterialFlipper(context: Context) {
|
||||||
fun forSmallComponent(context: Context) =
|
private val fader = MaterialFader.forLargeComponent(context)
|
||||||
Anim(
|
|
||||||
MotionUtils.resolveThemeInterpolator(
|
|
||||||
context,
|
|
||||||
MR.attr.motionEasingStandardAccelerateInterpolator,
|
|
||||||
FastOutSlowInInterpolator()),
|
|
||||||
MotionUtils.resolveThemeDuration(context, MR.attr.motionDurationShort2, 100).toLong())
|
|
||||||
|
|
||||||
fun forMediumComponent(context: Context) =
|
fun jump(from: View) {
|
||||||
Anim(
|
fader.jumpToFadeOut(from)
|
||||||
MotionUtils.resolveThemeInterpolator(
|
}
|
||||||
context,
|
|
||||||
MR.attr.motionEasingEmphasizedAccelerateInterpolator,
|
fun flip(from: View, to: View): Animator {
|
||||||
FastOutSlowInInterpolator()),
|
val outAnimator = fader.fadeOut(from)
|
||||||
MotionUtils.resolveThemeDuration(context, MR.attr.motionDurationShort4, 250).toLong())
|
val inAnimator = fader.fadeIn(to).apply { startDelay = outAnimator.totalDuration }
|
||||||
|
return AnimatorSet().apply { playTogether(outAnimator, inAnimator) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,33 +18,27 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
import android.animation.AnimatorSet
|
import android.animation.Animator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
class MultiToolbar
|
class MultiToolbar
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
FrameLayout(context, attrs, defStyleAttr) {
|
FrameLayout(context, attrs, defStyleAttr) {
|
||||||
private var animator: AnimatorSet? = null
|
private var animator: Animator? = null
|
||||||
private var currentlyVisible = 0
|
private var currentlyVisible = 0
|
||||||
private val outAnim = OutAnim.forMediumComponent(context)
|
private val flipper = MaterialFlipper(context)
|
||||||
private val inAnim = InAnim.forMediumComponent(context)
|
|
||||||
|
|
||||||
override fun onFinishInflate() {
|
override fun onFinishInflate() {
|
||||||
super.onFinishInflate()
|
super.onFinishInflate()
|
||||||
for (i in 1 until childCount) {
|
for (i in 1 until childCount) {
|
||||||
getChildAt(i).apply {
|
getChildAt(i).apply { flipper.jump(this) }
|
||||||
alpha = 0f
|
|
||||||
isInvisible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,56 +53,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
// TODO: Animate nicer Material Fade transitions using animators (Normal transitions
|
// TODO: Animate nicer Material Fade transitions using animators (Normal transitions
|
||||||
// don't work due to translation)
|
// don't work due to translation)
|
||||||
// Set up the target transitions for both the inner and selection toolbars.
|
// Set up the target transitions for both the inner and selection toolbars.
|
||||||
val targetFromAlpha = 0f
|
|
||||||
val targetToAlpha = 1f
|
|
||||||
val fromView = getChildAt(from) as Toolbar
|
|
||||||
val toView = getChildAt(to) as Toolbar
|
|
||||||
|
|
||||||
if (fromView.alpha == targetFromAlpha && toView.alpha == targetToAlpha) {
|
|
||||||
// Nothing to do.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLaidOut) {
|
|
||||||
// Not laid out, just change it immediately while are not shown to the user.
|
|
||||||
// This is an initialization, so we return false despite changing.
|
|
||||||
L.d("Not laid out, immediately updating visibility")
|
|
||||||
fromView.apply {
|
|
||||||
alpha = 0f
|
|
||||||
isInvisible = true
|
|
||||||
}
|
|
||||||
toView.apply {
|
|
||||||
alpha = 1f
|
|
||||||
isInvisible = false
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
L.d("Changing toolbar visibility $from -> 0f, $to -> 1f")
|
L.d("Changing toolbar visibility $from -> 0f, $to -> 1f")
|
||||||
animator?.cancel()
|
animator?.cancel()
|
||||||
val outAnimator =
|
animator = flipper.flip(getChildAt(from), getChildAt(to)).also { it.start() }
|
||||||
outAnim.genericFloat(fromView.alpha, 0f) {
|
|
||||||
fromView.apply {
|
|
||||||
scaleX = 1 - 0.05f * (1 - it)
|
|
||||||
scaleY = 1 - 0.05f * (1 - it)
|
|
||||||
alpha = it
|
|
||||||
isInvisible = alpha == 0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val inAnimator =
|
|
||||||
inAnim.genericFloat(toView.alpha, 1f, outAnim.duration) {
|
|
||||||
toView.apply {
|
|
||||||
scaleX = 1 - 0.05f * (1 - it)
|
|
||||||
scaleY = 1 - 0.05f * (1 - it)
|
|
||||||
alpha = it
|
|
||||||
isInvisible = alpha == 0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animator =
|
|
||||||
AnimatorSet().apply {
|
|
||||||
playTogether(outAnimator, inAnimator)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue