Merge "[Media TTT] Add bounce animation to the receiver icon" into tm-qpr-dev

This commit is contained in:
Michael Mikhail
2023-02-08 09:52:02 +00:00
committed by Android (Google) Code Review
2 changed files with 87 additions and 34 deletions

View File

@@ -27,6 +27,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
/> />
<FrameLayout
android:id="@+id/icon_container_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
android:id="@+id/icon_glow_ripple" android:id="@+id/icon_glow_ripple"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -41,8 +46,8 @@
android:layout_width="@dimen/media_ttt_icon_size_receiver" android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver" android:layout_height="@dimen/media_ttt_icon_size_receiver"
android:layout_gravity="center|bottom" android:layout_gravity="center|bottom"
android:alpha="0.0"
android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin" android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
/> />
</FrameLayout>
</FrameLayout> </FrameLayout>

View File

@@ -16,7 +16,9 @@
package com.android.systemui.media.taptotransfer.receiver package com.android.systemui.media.taptotransfer.receiver
import android.animation.TimeInterpolator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.animation.ValueAnimator
import android.app.StatusBarManager import android.app.StatusBarManager
import android.content.Context import android.content.Context
import android.graphics.Rect import android.graphics.Rect
@@ -31,8 +33,10 @@ import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.TintedIconViewBinder import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
@@ -101,6 +105,13 @@ open class MediaTttChipControllerReceiver @Inject constructor(
fitInsetsTypes = 0 // Ignore insets from all system bars fitInsetsTypes = 0 // Ignore insets from all system bars
} }
// Value animator that controls the bouncing animation of views.
private val bounceAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.REVERSE
duration = ICON_BOUNCE_ANIM_DURATION
}
private val commandQueueCallbacks = object : CommandQueue.Callbacks { private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay( override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int, @StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -203,44 +214,52 @@ open class MediaTttChipControllerReceiver @Inject constructor(
val iconView = currentView.getAppIconView() val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView) TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
val iconContainerView = currentView.getIconContainerView()
iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
} }
override fun animateViewIn(view: ViewGroup) { override fun animateViewIn(view: ViewGroup) {
val appIconView = view.getAppIconView() val iconContainerView = view.getIconContainerView()
val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple) val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple) val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f) val translationYBy = getTranslationAmount()
animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f) // Make the icon container view starts animation from bottom of the screen.
iconContainerView.translationY += rippleController.getReceiverIconSize()
animateViewTranslationAndFade(
iconContainerView,
translationYBy = -1 * translationYBy,
alphaEndValue = 1f,
Interpolators.EMPHASIZED_DECELERATE,
) {
animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO)
}
rippleController.expandToInProgressState(rippleView, iconRippleView) rippleController.expandToInProgressState(rippleView, iconRippleView)
} }
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) { override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val appIconView = view.getAppIconView() val iconContainerView = view.getIconContainerView()
val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple) val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
val translationYBy = getTranslationAmount()
// Remove update listeners from bounce animator to prevent any conflict with
// translation animation.
bounceAnimator.removeAllUpdateListeners()
bounceAnimator.cancel()
if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name && if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) { mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
rippleController.expandToSuccessState(rippleView, onAnimationEnd) rippleController.expandToSuccessState(rippleView, onAnimationEnd)
animateViewTranslationAndFade( animateViewTranslationAndFade(
iconRippleView, iconContainerView,
-1 * getTranslationAmount(), -1 * translationYBy,
0f,
translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
)
animateViewTranslationAndFade(
appIconView,
-1 * getTranslationAmount(),
0f, 0f,
translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION, translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION, alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
) )
} else { } else {
rippleController.collapseRipple(rippleView, onAnimationEnd) rippleController.collapseRipple(rippleView, onAnimationEnd)
animateViewTranslationAndFade(iconRippleView, getTranslationAmount(), 0f) animateViewTranslationAndFade(iconContainerView, translationYBy, 0f)
animateViewTranslationAndFade(appIconView, getTranslationAmount(), 0f)
} }
} }
@@ -252,15 +271,19 @@ open class MediaTttChipControllerReceiver @Inject constructor(
/** Animation of view translation and fading. */ /** Animation of view translation and fading. */
private fun animateViewTranslationAndFade( private fun animateViewTranslationAndFade(
view: View, view: ViewGroup,
translationYBy: Float, translationYBy: Float,
alphaEndValue: Float, alphaEndValue: Float,
interpolator: TimeInterpolator? = null,
translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION, translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION,
alphaDuration: Long = ICON_ALPHA_ANIM_DURATION, alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
onAnimationEnd: Runnable? = null,
) { ) {
view.animate() view.animate()
.translationYBy(translationYBy) .translationYBy(translationYBy)
.setInterpolator(interpolator)
.setDuration(translationDuration) .setDuration(translationDuration)
.withEndAction { onAnimationEnd?.run() }
.start() .start()
view.animate() view.animate()
.alpha(alphaEndValue) .alpha(alphaEndValue)
@@ -270,17 +293,42 @@ open class MediaTttChipControllerReceiver @Inject constructor(
/** Returns the amount that the chip will be translated by in its intro animation. */ /** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Float { private fun getTranslationAmount(): Float {
return rippleController.getRippleSize() * 0.5f - return rippleController.getRippleSize() * 0.5f
rippleController.getReceiverIconSize()
} }
private fun View.getAppIconView(): CachingIconView { private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon) return this.requireViewById(R.id.app_icon)
} }
private fun View.getIconContainerView(): ViewGroup {
return this.requireViewById(R.id.icon_container_view)
}
private fun animateBouncingView(iconContainerView: ViewGroup, translationYBy: Float) {
if (bounceAnimator.isStarted) {
return
}
addViewToBounceAnimation(iconContainerView, translationYBy)
// In order not to announce description every time the view animate.
iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
bounceAnimator.start()
}
private fun addViewToBounceAnimation(view: View, translationYBy: Float) {
val prevTranslationY = view.translationY
bounceAnimator.addUpdateListener { updateListener ->
val progress = updateListener.animatedValue as Float
view.translationY = prevTranslationY + translationYBy * progress
}
}
companion object { companion object {
private const val ICON_TRANSLATION_ANIM_DURATION = 500L private const val ICON_TRANSLATION_ANIM_DURATION = 500L
private const val ICON_BOUNCE_ANIM_DURATION = 750L
private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L
private const val BOUNCE_TRANSLATION_RATIO = 0.15f
private val ICON_ALPHA_ANIM_DURATION = 5.frames private val ICON_ALPHA_ANIM_DURATION = 5.frames
} }
} }