Merge "[Media TTT] Add animation out for receiver chip" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9391ef81b5
@@ -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?,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user