Merge "[Media TTT] Add animation out for receiver chip" into tm-qpr-dev

This commit is contained in:
Michael Mikhail
2022-11-18 00:52:52 +00:00
committed by Android (Google) Code Review
5 changed files with 130 additions and 8 deletions

View File

@@ -56,7 +56,7 @@ import javax.inject.Inject
* TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
*/ */
@SysUISingleton @SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor( open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue, private val commandQueue: CommandQueue,
context: Context, context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger, @MediaTttReceiverLogger logger: MediaTttLogger,
@@ -183,15 +183,28 @@ class MediaTttChipControllerReceiver @Inject constructor(
val appIconView = view.getAppIconView() val appIconView = view.getAppIconView()
appIconView.animate() appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat()) .translationYBy(-1 * getTranslationAmount().toFloat())
.setDuration(30.frames) .setDuration(ICON_TRANSLATION_ANIM_DURATION)
.start() .start()
appIconView.animate() appIconView.animate()
.alpha(1f) .alpha(1f)
.setDuration(5.frames) .setDuration(ICON_ALPHA_ANIM_DURATION)
.start() .start()
// Using withEndAction{} doesn't apply a11y focus when screen is unlocked. // Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
appIconView.postOnAnimation { view.requestAccessibilityFocus() } appIconView.postOnAnimation { view.requestAccessibilityFocus() }
startRipple(view.requireViewById(R.id.ripple)) expandRipple(view.requireViewById(R.id.ripple))
}
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(getTranslationAmount().toFloat())
.setDuration(ICON_TRANSLATION_ANIM_DURATION)
.start()
appIconView.animate()
.alpha(0f)
.setDuration(ICON_ALPHA_ANIM_DURATION)
.start()
(view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
} }
override fun getTouchableRegion(view: View, outRect: Rect) { override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -205,11 +218,22 @@ class MediaTttChipControllerReceiver @Inject constructor(
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
} }
private fun startRipple(rippleView: ReceiverChipRippleView) { private fun expandRipple(rippleView: ReceiverChipRippleView) {
if (rippleView.rippleInProgress()) { if (rippleView.rippleInProgress()) {
// Skip if ripple is still playing // Skip if ripple is still playing
return return
} }
// In case the device orientation changes, we need to reset the layout.
rippleView.addOnLayoutChangeListener (
View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
if (v == null) return@OnLayoutChangeListener
val layoutChangedRippleView = v as ReceiverChipRippleView
layoutRipple(layoutChangedRippleView)
layoutChangedRippleView.invalidate()
}
)
rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(view: View?) {} override fun onViewDetachedFromWindow(view: View?) {}
@@ -219,7 +243,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
} }
val attachedRippleView = view as ReceiverChipRippleView val attachedRippleView = view as ReceiverChipRippleView
layoutRipple(attachedRippleView) layoutRipple(attachedRippleView)
attachedRippleView.startRipple() attachedRippleView.expandRipple()
attachedRippleView.removeOnAttachStateChangeListener(this) attachedRippleView.removeOnAttachStateChangeListener(this)
} }
}) })
@@ -242,6 +266,9 @@ class MediaTttChipControllerReceiver @Inject constructor(
} }
} }
val ICON_TRANSLATION_ANIM_DURATION = 30.frames
val ICON_ALPHA_ANIM_DURATION = 5.frames
data class ChipReceiverInfo( data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info, val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?, val appIconDrawableOverride: Drawable?,

View File

@@ -16,6 +16,8 @@
package com.android.systemui.media.taptotransfer.receiver package com.android.systemui.media.taptotransfer.receiver
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import com.android.systemui.surfaceeffects.ripple.RippleShader import com.android.systemui.surfaceeffects.ripple.RippleShader
@@ -25,10 +27,36 @@ import com.android.systemui.surfaceeffects.ripple.RippleView
* An expanding ripple effect for the media tap-to-transfer receiver chip. * An expanding ripple effect for the media tap-to-transfer receiver chip.
*/ */
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
// Indicates whether the ripple started expanding.
private var isStarted: Boolean
init { init {
setupShader(RippleShader.RippleShape.ELLIPSE) setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true) setRippleFill(true)
setSparkleStrength(0f) setSparkleStrength(0f)
duration = 3000L duration = 3000L
isStarted = false
}
fun expandRipple(onAnimationEnd: Runnable? = null) {
isStarted = true
super.startRipple(onAnimationEnd)
}
/** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */
fun collapseRipple(onAnimationEnd: Runnable? = null) {
if (!isStarted) {
return // Ignore if ripple is not started yet.
}
// Reset all listeners to animator.
animator.removeAllListeners()
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run()
isStarted = false
}
})
animator.reverse()
} }
} }

View File

@@ -41,7 +41,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
private set private set
private val ripplePaint = Paint() private val ripplePaint = Paint()
private val animator = ValueAnimator.ofFloat(0f, 1f) protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
var duration: Long = 1750 var duration: Long = 1750

View File

@@ -0,0 +1,67 @@
/*
* 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.taptotransfer.receiver
import android.content.Context
import android.os.Handler
import android.os.PowerManager
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
class FakeMediaTttChipControllerReceiver(
commandQueue: CommandQueue,
context: Context,
logger: MediaTttLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
mainHandler: Handler,
mediaTttFlags: MediaTttFlags,
uiEventLogger: MediaTttReceiverUiEventLogger,
viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
) :
MediaTttChipControllerReceiver(
commandQueue,
context,
logger,
windowManager,
mainExecutor,
accessibilityManager,
configurationController,
powerManager,
mainHandler,
mediaTttFlags,
uiEventLogger,
viewUtil,
wakeLockBuilder,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
onAnimationEnd.run()
}
}

View File

@@ -114,7 +114,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder = WakeLockFake.Builder(context)
fakeWakeLockBuilder.setWakeLock(fakeWakeLock) fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
controllerReceiver = MediaTttChipControllerReceiver( controllerReceiver = FakeMediaTttChipControllerReceiver(
commandQueue, commandQueue,
context, context,
logger, logger,