Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf4e0558 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update prev/next media player animation" into tm-dev

parents 669a1c26 70e724d1
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="together">
    <objectAnimator
        android:propertyName="translationX"
        android:valueFrom="32dp"
        android:valueTo="0dp"
        android:duration="600"
        android:valueType="floatType" />
    <objectAnimator
        android:propertyName="transitionAlpha"
        android:valueFrom="0"
        android:valueTo="1"
        android:duration="167"
        android:valueType="floatType" />
</set>
 No newline at end of file
+32 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="together">
    <objectAnimator
        android:propertyName="translationX"
        android:valueFrom="0dp"
        android:valueTo="-32dp"
        android:duration="167"
        android:valueType="floatType" />
    <objectAnimator
        android:propertyName="transitionAlpha"
        android:valueFrom="1"
        android:valueTo="0"
        android:duration="83"
        android:startOffset="83"
        android:valueType="floatType" />
</set>
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.media

import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable

/**
 * AnimationBindHandler is responsible for tracking the bound animation state and preventing
 * jank and conflicts due to media notifications arriving at any time during an animation. It
 * does this in two parts.
 *  - Exit animations fired as a result of user input are tracked. When these are running, any
 *      bind actions are delayed until the animation completes (and then fired in sequence).
 *  - Continuous animations are tracked using their rebind id. Later calls using the same
 *      rebind id will be totally ignored to prevent the continuous animation from restarting.
 */
internal class AnimationBindHandler : Animatable2.AnimationCallback() {
    private val onAnimationsComplete = mutableListOf<() -> Unit>()
    private val registrations = mutableListOf<Animatable2>()
    private var rebindId: Int? = null

    val isAnimationRunning: Boolean
        get() = registrations.any { it.isRunning }

    /**
     * This check prevents rebinding to the action button if the identifier has not changed. A
     * null value is always considered to be changed. This is used to prevent the connecting
     * animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
     * an application in a row.
     */
    fun updateRebindId(newRebindId: Int?): Boolean {
        if (rebindId == null || newRebindId == null || rebindId != newRebindId) {
            rebindId = newRebindId
            return true
        }
        return false
    }

    fun tryRegister(drawable: Drawable?) {
        if (drawable is Animatable2) {
            val anim = drawable as Animatable2
            anim.registerAnimationCallback(this)
            registrations.add(anim)
        }
    }

    fun unregisterAll() {
        registrations.forEach { it.unregisterAnimationCallback(this) }
        registrations.clear()
    }

    fun tryExecute(action: () -> Unit) {
        if (isAnimationRunning) {
            onAnimationsComplete.add(action)
        } else {
            action()
        }
    }

    override fun onAnimationEnd(drawable: Drawable) {
        super.onAnimationEnd(drawable)
        if (!isAnimationRunning) {
            onAnimationsComplete.forEach { it() }
            onAnimationsComplete.clear()
        }
    }
}
 No newline at end of file
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.media

import android.animation.ArgbEvaluator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.ColorStateList
import com.android.internal.R
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.monet.ColorScheme

/**
 * ColorTransition is responsible for managing the animation between two specific colors.
 * It uses a ValueAnimator to execute the animation and interpolate between the source color and
 * the target color.
 *
 * Selection of the target color from the scheme, and application of the interpolated color
 * are delegated to callbacks.
 */
open class ColorTransition(
    private val defaultColor: Int,
    private val extractColor: (ColorScheme) -> Int,
    private val applyColor: (Int) -> Unit
) : AnimatorUpdateListener {

    private val argbEvaluator = ArgbEvaluator()
    private val valueAnimator = buildAnimator()
    var sourceColor: Int = defaultColor
    var currentColor: Int = defaultColor
    var targetColor: Int = defaultColor

    override fun onAnimationUpdate(animation: ValueAnimator) {
        currentColor = argbEvaluator.evaluate(
            animation.animatedFraction, sourceColor, targetColor
        ) as Int
        applyColor(currentColor)
    }

    fun updateColorScheme(scheme: ColorScheme?) {
        val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
        if (newTargetColor != targetColor) {
            sourceColor = currentColor
            targetColor = newTargetColor
            valueAnimator.cancel()
            valueAnimator.start()
        }
    }

    init {
        applyColor(defaultColor)
    }

    @VisibleForTesting
    open fun buildAnimator(): ValueAnimator {
        val animator = ValueAnimator.ofFloat(0f, 1f)
        animator.duration = 333
        animator.addUpdateListener(this)
        return animator
    }
}

typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> ColorTransition

/**
 * ColorSchemeTransition constructs a ColorTransition for each color in the scheme
 * that needs to be transitioned when changed. It also sets up the assignment functions for sending
 * the sending the interpolated colors to the appropriate views.
 */
class ColorSchemeTransition internal constructor(
    private val context: Context,
    bgColor: Int,
    mediaViewHolder: MediaViewHolder,
    colorTransitionFactory: ColorTransitionFactory
) {
    constructor(context: Context, bgColor: Int, mediaViewHolder: MediaViewHolder) :
        this(context, bgColor, mediaViewHolder, ::ColorTransition)

    val surfaceColor = colorTransitionFactory(
        bgColor,
        { colorScheme -> colorScheme.accent2[9] }, // A2-800
        { surfaceColor ->
            val colorList = ColorStateList.valueOf(surfaceColor)
            mediaViewHolder.player.backgroundTintList = colorList
            mediaViewHolder.albumView.foregroundTintList = colorList
            mediaViewHolder.albumView.backgroundTintList = colorList
            mediaViewHolder.seamlessIcon.imageTintList = colorList
            mediaViewHolder.seamlessText.setTextColor(surfaceColor)
            mediaViewHolder.dismissText.setTextColor(surfaceColor)
        })

    val accentPrimary = colorTransitionFactory(
        loadDefaultColor(R.attr.textColorPrimary),
        { colorScheme -> colorScheme.accent1[2] }, // A1-100
        { accentPrimary ->
            val accentColorList = ColorStateList.valueOf(accentPrimary)
            mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
            mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
            mediaViewHolder.settings.imageTintList = accentColorList
            mediaViewHolder.cancelText.backgroundTintList = accentColorList
            mediaViewHolder.dismissText.backgroundTintList = accentColorList
        })

    val textPrimary = colorTransitionFactory(
        loadDefaultColor(R.attr.textColorPrimary),
        { colorScheme -> colorScheme.neutral1[1] }, // N1-50
        { textPrimary ->
            mediaViewHolder.titleText.setTextColor(textPrimary)
            val textColorList = ColorStateList.valueOf(textPrimary)
            mediaViewHolder.seekBar.thumb.setTintList(textColorList)
            mediaViewHolder.seekBar.progressTintList = textColorList
            mediaViewHolder.longPressText.setTextColor(textColorList)
            mediaViewHolder.cancelText.setTextColor(textColorList)
            mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
            mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
            for (button in mediaViewHolder.getTransparentActionButtons()) {
                button.imageTintList = textColorList
            }
        })

    val textPrimaryInverse = colorTransitionFactory(
        loadDefaultColor(R.attr.textColorPrimaryInverse),
        { colorScheme -> colorScheme.neutral1[10] }, // N1-900
        { textPrimaryInverse ->
            mediaViewHolder.actionPlayPause.imageTintList =
                ColorStateList.valueOf(textPrimaryInverse)
        })

    val textSecondary = colorTransitionFactory(
        loadDefaultColor(R.attr.textColorSecondary),
        { colorScheme -> colorScheme.neutral2[3] }, // N2-200
        { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) })

    val textTertiary = colorTransitionFactory(
        loadDefaultColor(R.attr.textColorTertiary),
        { colorScheme -> colorScheme.neutral2[5] }, // N2-400
        { textTertiary ->
            mediaViewHolder.seekBar.progressBackgroundTintList =
                ColorStateList.valueOf(textTertiary)
        })

    val colorTransitions = arrayOf(
        surfaceColor, accentPrimary, textPrimary,
        textPrimaryInverse, textSecondary, textTertiary)

    private fun loadDefaultColor(id: Int): Int {
        return Utils.getColorAttr(context, id).defaultColor
    }

    fun updateColorScheme(colorScheme: ColorScheme?) {
        colorTransitions.forEach { it.updateColorScheme(colorScheme) }
    }
}
 No newline at end of file
+191 −205

File changed.

Preview size limit exceeded, changes collapsed.

Loading