Loading packages/SystemUI/res/layout/screenshot_shelf.xml +7 −0 Original line number Diff line number Diff line Loading @@ -147,4 +147,11 @@ <include layout="@layout/screenshot_work_profile_first_run" /> <include layout="@layout/screenshot_detection_notice" /> </FrameLayout> <ImageView android:id="@+id/screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" android:elevation="12dp" android:src="@android:color/white"/> </com.android.systemui.screenshot.ui.ScreenshotShelfView> packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +5 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.view.WindowInsets import android.view.WindowManager import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.core.animation.doOnEnd import com.android.internal.logging.UiEventLogger import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R Loading Loading @@ -109,7 +110,10 @@ constructor( override fun updateOrientation(insets: WindowInsets) {} override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator { return animationController.getEntranceAnimation() val entrance = animationController.getEntranceAnimation(screenRect, showFlash) // reset the timeout when animation finishes entrance.doOnEnd { callbacks?.onUserInteraction() } return entrance } override fun addQuickShareChip(quickShareAction: Notification.Action) {} Loading packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +97 −14 Original line number Diff line number Diff line Loading @@ -17,9 +17,16 @@ package com.android.systemui.screenshot.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.graphics.PointF import android.graphics.Rect import android.util.MathUtils import android.view.View import android.view.animation.AnimationUtils import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.systemui.res.R import kotlin.math.abs import kotlin.math.max Loading @@ -27,23 +34,57 @@ import kotlin.math.sign class ScreenshotAnimationController(private val view: ScreenshotShelfView) { private var animator: Animator? = null private val screenshotPreview = view.requireViewById<View>(R.id.screenshot_preview) private val flashView = view.requireViewById<View>(R.id.screenshot_flash) private val actionContainer = view.requireViewById<View>(R.id.actions_container_background) private val fastOutSlowIn = AnimationUtils.loadInterpolator(view.context, android.R.interpolator.fast_out_slow_in) private val staticUI = listOf<View>( view.requireViewById(R.id.screenshot_preview_border), view.requireViewById(R.id.actions_container_background), view.requireViewById(R.id.screenshot_badge), view.requireViewById(R.id.screenshot_dismiss_button) ) fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator { val entranceAnimation = AnimatorSet() val previewAnimator = getPreviewAnimator(bounds) fun getEntranceAnimation(): Animator { val animator = ValueAnimator.ofFloat(0f, 1f) animator.addUpdateListener { view.alpha = it.animatedFraction } animator.addListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animator: Animator) { view.alpha = 0f if (showFlash) { val flashInAnimator = ObjectAnimator.ofFloat(flashView, "alpha", 0f, 1f).apply { duration = FLASH_IN_DURATION_MS interpolator = fastOutSlowIn } override fun onAnimationEnd(animator: Animator) { view.alpha = 1f val flashOutAnimator = ObjectAnimator.ofFloat(flashView, "alpha", 1f, 0f).apply { duration = FLASH_OUT_DURATION_MS interpolator = fastOutSlowIn } flashInAnimator.doOnStart { flashView.visibility = View.VISIBLE } flashOutAnimator.doOnEnd { flashView.visibility = View.GONE } entranceAnimation.play(flashOutAnimator).after(flashInAnimator) entranceAnimation.play(previewAnimator).with(flashOutAnimator) entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE } } val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f) fadeInAnimator.addUpdateListener { for (child in staticUI) { child.alpha = it.animatedValue as Float } } entranceAnimation.play(fadeInAnimator).after(previewAnimator) entranceAnimation.doOnStart { for (child in staticUI) { child.alpha = 0f } ) this.animator = animator return animator } this.animator = entranceAnimation return entranceAnimation } fun getSwipeReturnAnimation(): Animator { Loading Loading @@ -81,11 +122,49 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { animator?.cancel() } private fun getPreviewAnimator(bounds: Rect): Animator { val targetPosition = Rect() screenshotPreview.getHitRect(targetPosition) val startXScale = bounds.width() / targetPosition.width().toFloat() val startYScale = bounds.height() / targetPosition.height().toFloat() val startPos = PointF(bounds.exactCenterX(), bounds.exactCenterY()) val endPos = PointF(targetPosition.exactCenterX(), targetPosition.exactCenterY()) val previewYAnimator = ValueAnimator.ofFloat(startPos.y, endPos.y).apply { duration = PREVIEW_Y_ANIMATION_DURATION_MS interpolator = fastOutSlowIn } previewYAnimator.addUpdateListener { val progress = it.animatedValue as Float screenshotPreview.y = progress - screenshotPreview.height / 2f } // scale animation starts/finishes at the same time as x placement val previewXAndScaleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = PREVIEW_X_ANIMATION_DURATION_MS interpolator = fastOutSlowIn } previewXAndScaleAnimator.addUpdateListener { val t = it.animatedFraction screenshotPreview.scaleX = MathUtils.lerp(startXScale, 1f, t) screenshotPreview.scaleY = MathUtils.lerp(startYScale, 1f, t) screenshotPreview.x = MathUtils.lerp(startPos.x, endPos.x, t) - screenshotPreview.width / 2f } val previewAnimator = AnimatorSet() previewAnimator.play(previewXAndScaleAnimator).with(previewYAnimator) previewAnimator.doOnStart { screenshotPreview.visibility = View.VISIBLE } return previewAnimator } private fun getAdjustedVelocity(requestedVelocity: Float?): Float { return if (requestedVelocity == null) { val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR // dismiss to the left in LTR locales, to the right in RTL if (isLTR) -1 * MINIMUM_VELOCITY else MINIMUM_VELOCITY if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY } else { sign(requestedVelocity) * max(MINIMUM_VELOCITY, abs(requestedVelocity)) } Loading @@ -93,5 +172,9 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { companion object { private const val MINIMUM_VELOCITY = 1.5f // pixels per second private const val FLASH_IN_DURATION_MS: Long = 133 private const val FLASH_OUT_DURATION_MS: Long = 217 private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234 private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500 } } packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt +1 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ import kotlin.math.abs class SwipeGestureListener( private val view: View, private val onDismiss: (Float) -> Unit, private val onDismiss: (Float?) -> Unit, private val onCancel: () -> Unit ) { private val velocityTracker = VelocityTracker.obtain() Loading packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +6 −4 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.systemui.screenshot.ui.ScreenshotShelfView import com.android.systemui.screenshot.ui.SwipeGestureListener import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.children import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch object ScreenshotShelfViewBinder { Loading Loading @@ -60,7 +61,8 @@ object ScreenshotShelfViewBinder { onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null) } view.repeatWhenAttached { // use immediate dispatcher to ensure screenshot bitmap is set before animation view.repeatWhenAttached(Dispatchers.Main.immediate) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { Loading Loading @@ -96,9 +98,9 @@ object ScreenshotShelfViewBinder { // ID is unique. val newIds = visibleActions.map { it.id } for (view in actionsContainer.children.toList()) { if (view.tag !in newIds) { actionsContainer.removeView(view) for (child in actionsContainer.children.toList()) { if (child.tag !in newIds) { actionsContainer.removeView(child) } } Loading Loading
packages/SystemUI/res/layout/screenshot_shelf.xml +7 −0 Original line number Diff line number Diff line Loading @@ -147,4 +147,11 @@ <include layout="@layout/screenshot_work_profile_first_run" /> <include layout="@layout/screenshot_detection_notice" /> </FrameLayout> <ImageView android:id="@+id/screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" android:elevation="12dp" android:src="@android:color/white"/> </com.android.systemui.screenshot.ui.ScreenshotShelfView>
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +5 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.view.WindowInsets import android.view.WindowManager import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.core.animation.doOnEnd import com.android.internal.logging.UiEventLogger import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R Loading Loading @@ -109,7 +110,10 @@ constructor( override fun updateOrientation(insets: WindowInsets) {} override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator { return animationController.getEntranceAnimation() val entrance = animationController.getEntranceAnimation(screenRect, showFlash) // reset the timeout when animation finishes entrance.doOnEnd { callbacks?.onUserInteraction() } return entrance } override fun addQuickShareChip(quickShareAction: Notification.Action) {} Loading
packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +97 −14 Original line number Diff line number Diff line Loading @@ -17,9 +17,16 @@ package com.android.systemui.screenshot.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.graphics.PointF import android.graphics.Rect import android.util.MathUtils import android.view.View import android.view.animation.AnimationUtils import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.systemui.res.R import kotlin.math.abs import kotlin.math.max Loading @@ -27,23 +34,57 @@ import kotlin.math.sign class ScreenshotAnimationController(private val view: ScreenshotShelfView) { private var animator: Animator? = null private val screenshotPreview = view.requireViewById<View>(R.id.screenshot_preview) private val flashView = view.requireViewById<View>(R.id.screenshot_flash) private val actionContainer = view.requireViewById<View>(R.id.actions_container_background) private val fastOutSlowIn = AnimationUtils.loadInterpolator(view.context, android.R.interpolator.fast_out_slow_in) private val staticUI = listOf<View>( view.requireViewById(R.id.screenshot_preview_border), view.requireViewById(R.id.actions_container_background), view.requireViewById(R.id.screenshot_badge), view.requireViewById(R.id.screenshot_dismiss_button) ) fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator { val entranceAnimation = AnimatorSet() val previewAnimator = getPreviewAnimator(bounds) fun getEntranceAnimation(): Animator { val animator = ValueAnimator.ofFloat(0f, 1f) animator.addUpdateListener { view.alpha = it.animatedFraction } animator.addListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animator: Animator) { view.alpha = 0f if (showFlash) { val flashInAnimator = ObjectAnimator.ofFloat(flashView, "alpha", 0f, 1f).apply { duration = FLASH_IN_DURATION_MS interpolator = fastOutSlowIn } override fun onAnimationEnd(animator: Animator) { view.alpha = 1f val flashOutAnimator = ObjectAnimator.ofFloat(flashView, "alpha", 1f, 0f).apply { duration = FLASH_OUT_DURATION_MS interpolator = fastOutSlowIn } flashInAnimator.doOnStart { flashView.visibility = View.VISIBLE } flashOutAnimator.doOnEnd { flashView.visibility = View.GONE } entranceAnimation.play(flashOutAnimator).after(flashInAnimator) entranceAnimation.play(previewAnimator).with(flashOutAnimator) entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE } } val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f) fadeInAnimator.addUpdateListener { for (child in staticUI) { child.alpha = it.animatedValue as Float } } entranceAnimation.play(fadeInAnimator).after(previewAnimator) entranceAnimation.doOnStart { for (child in staticUI) { child.alpha = 0f } ) this.animator = animator return animator } this.animator = entranceAnimation return entranceAnimation } fun getSwipeReturnAnimation(): Animator { Loading Loading @@ -81,11 +122,49 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { animator?.cancel() } private fun getPreviewAnimator(bounds: Rect): Animator { val targetPosition = Rect() screenshotPreview.getHitRect(targetPosition) val startXScale = bounds.width() / targetPosition.width().toFloat() val startYScale = bounds.height() / targetPosition.height().toFloat() val startPos = PointF(bounds.exactCenterX(), bounds.exactCenterY()) val endPos = PointF(targetPosition.exactCenterX(), targetPosition.exactCenterY()) val previewYAnimator = ValueAnimator.ofFloat(startPos.y, endPos.y).apply { duration = PREVIEW_Y_ANIMATION_DURATION_MS interpolator = fastOutSlowIn } previewYAnimator.addUpdateListener { val progress = it.animatedValue as Float screenshotPreview.y = progress - screenshotPreview.height / 2f } // scale animation starts/finishes at the same time as x placement val previewXAndScaleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = PREVIEW_X_ANIMATION_DURATION_MS interpolator = fastOutSlowIn } previewXAndScaleAnimator.addUpdateListener { val t = it.animatedFraction screenshotPreview.scaleX = MathUtils.lerp(startXScale, 1f, t) screenshotPreview.scaleY = MathUtils.lerp(startYScale, 1f, t) screenshotPreview.x = MathUtils.lerp(startPos.x, endPos.x, t) - screenshotPreview.width / 2f } val previewAnimator = AnimatorSet() previewAnimator.play(previewXAndScaleAnimator).with(previewYAnimator) previewAnimator.doOnStart { screenshotPreview.visibility = View.VISIBLE } return previewAnimator } private fun getAdjustedVelocity(requestedVelocity: Float?): Float { return if (requestedVelocity == null) { val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR // dismiss to the left in LTR locales, to the right in RTL if (isLTR) -1 * MINIMUM_VELOCITY else MINIMUM_VELOCITY if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY } else { sign(requestedVelocity) * max(MINIMUM_VELOCITY, abs(requestedVelocity)) } Loading @@ -93,5 +172,9 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { companion object { private const val MINIMUM_VELOCITY = 1.5f // pixels per second private const val FLASH_IN_DURATION_MS: Long = 133 private const val FLASH_OUT_DURATION_MS: Long = 217 private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234 private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500 } }
packages/SystemUI/src/com/android/systemui/screenshot/ui/SwipeGestureListener.kt +1 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ import kotlin.math.abs class SwipeGestureListener( private val view: View, private val onDismiss: (Float) -> Unit, private val onDismiss: (Float?) -> Unit, private val onCancel: () -> Unit ) { private val velocityTracker = VelocityTracker.obtain() Loading
packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +6 −4 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.systemui.screenshot.ui.ScreenshotShelfView import com.android.systemui.screenshot.ui.SwipeGestureListener import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.children import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch object ScreenshotShelfViewBinder { Loading Loading @@ -60,7 +61,8 @@ object ScreenshotShelfViewBinder { onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null) } view.repeatWhenAttached { // use immediate dispatcher to ensure screenshot bitmap is set before animation view.repeatWhenAttached(Dispatchers.Main.immediate) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { Loading Loading @@ -96,9 +98,9 @@ object ScreenshotShelfViewBinder { // ID is unique. val newIds = visibleActions.map { it.id } for (view in actionsContainer.children.toList()) { if (view.tag !in newIds) { actionsContainer.removeView(view) for (child in actionsContainer.children.toList()) { if (child.tag !in newIds) { actionsContainer.removeView(child) } } Loading