Loading packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -726,6 +726,16 @@ flag { bug: "362720389" } flag { name: "screenshot_dismissal_spring" namespace: "systemui" description: "Use a spring animator for screenshot dismissal, fade out after const distance" bug: "412986001" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "screenshot_policy_split_and_desktop_mode" namespace: "systemui" Loading packages/SystemUI/res/values/dimens.xml +2 −1 Original line number Diff line number Diff line Loading @@ -473,7 +473,8 @@ <!-- Dimensions related to screenshots --> <!-- Fade out the screenshot UI over this distance when dismissing --> <dimen name="dismissal_max_distance_fadeout">250dp</dimen> <dimen name="screenshot_crop_handle_thickness">3dp</dimen> <dimen name="long_screenshot_action_bar_top_margin">4dp</dimen> Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +32 −12 Original line number Diff line number Diff line Loading @@ -33,12 +33,14 @@ import android.view.View import android.view.ViewTreeObserver import android.view.WindowInsets import android.view.WindowManager import android.window.DesktopExperienceFlags import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.appcompat.content.res.AppCompatResources import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags import com.android.systemui.Flags.screenshotAnnounceLiveRegion import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R Loading Loading @@ -187,6 +189,14 @@ constructor( return } event?.let { logger.log(it, 0, packageName) } if (SCREENSHOT_DISMISSAL_SPRING.isTrue()) { animationController.startDismissal(velocity) { isDismissing = false callbacks?.onDismiss() } isDismissing = true } else { val animator = animationController.getSwipeDismissAnimation(velocity) animator.addListener( object : AnimatorListenerAdapter() { Loading @@ -202,6 +212,7 @@ constructor( ) animator.start() } } fun prepareScrollingTransition( response: ScrollCaptureResponse, Loading Loading @@ -362,6 +373,15 @@ constructor( ) } companion object { val SCREENSHOT_DISMISSAL_SPRING = DesktopExperienceFlags.DesktopExperienceFlag( Flags::screenshotDismissalSpring, /* shouldOverrideByDevOption= */ true, Flags.FLAG_SCREENSHOT_DISMISSAL_SPRING, ) } @AssistedFactory interface Factory { fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy Loading packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +61 −4 Original line number Diff line number Diff line Loading @@ -31,8 +31,13 @@ import android.util.MathUtils import android.view.View import android.view.animation.AnimationUtils import android.widget.ImageView import android.window.DesktopExperienceFlags import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.internal.dynamicanimation.animation.DynamicAnimation import com.android.internal.dynamicanimation.animation.SpringAnimation import com.android.internal.dynamicanimation.animation.SpringForce import com.android.systemui.Flags import com.android.systemui.res.R import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel Loading @@ -45,6 +50,7 @@ class ScreenshotAnimationController( private val viewModel: ScreenshotViewModel, ) { private var animator: Animator? = null private var dismissalSpring: SpringAnimation? = null private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview) private val scrollingScrim = view.requireViewById<ImageView>((R.id.screenshot_scrolling_scrim)) private val scrollTransitionPreview = Loading Loading @@ -132,6 +138,7 @@ class ScreenshotAnimationController( fun fadeForSharedTransition() { animator?.cancel() dismissalSpring?.cancel() val fadeAnimator = ValueAnimator.ofFloat(1f, 0f) fadeAnimator.addUpdateListener { for (view in fadeUI) { Loading Loading @@ -214,6 +221,7 @@ class ScreenshotAnimationController( } fun restoreUI() { dismissalSpring?.cancel() animator?.cancel() for (view in fadeUI) { view.alpha = 1f Loading @@ -229,6 +237,35 @@ class ScreenshotAnimationController( return animator } fun startDismissal(requestedVelocity: Float?, onEnd: () -> Unit) { dismissalSpring?.cancel() animator?.cancel() val velocity = getAdjustedVelocity(requestedVelocity) val maxDistanceFadeout = view.resources.getDimensionPixelOffset(R.dimen.dismissal_max_distance_fadeout).toFloat() val startingTranslation = view.translationX // Set the spring target to be a bit past the max translation distance val target = startingTranslation + maxDistanceFadeout * sign(velocity) * 1.2f dismissalSpring = SpringAnimation(view, DynamicAnimation.TRANSLATION_X, target).apply { setStartVelocity(velocity * 1000) setStartValue(startingTranslation) spring?.setStiffness(SpringForce.STIFFNESS_LOW) spring?.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) } dismissalSpring?.addUpdateListener { _, value, _ -> view.alpha = MathUtils.lerp(1f, 0f, abs(value - startingTranslation) / maxDistanceFadeout) } dismissalSpring?.addEndListener { _, _, _, _ -> onEnd.invoke() } dismissalSpring?.start() } fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator { animator?.cancel() val velocity = getAdjustedVelocity(requestedVelocity) Loading @@ -247,6 +284,7 @@ class ScreenshotAnimationController( view.translationX = it.animatedValue as Float view.alpha = 1f - it.animatedFraction } animator.duration = ((abs(distance / velocity))).toLong() animator.doOnStart { viewModel.setIsAnimating(true) } animator.doOnEnd { viewModel.setIsAnimating(false) } Loading @@ -257,6 +295,7 @@ class ScreenshotAnimationController( fun cancel() { animator?.cancel() dismissalSpring?.cancel() } private fun getActionsAnimator(): Animator { Loading Loading @@ -325,21 +364,39 @@ class ScreenshotAnimationController( private fun getAdjustedVelocity(requestedVelocity: Float?): Float { return if (requestedVelocity == null || abs(requestedVelocity) < .005f) { val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR if (SCREENSHOT_DISMISSAL_SPRING.isTrue) { if (viewClosestToLeftEdge()) -MINIMUM_VELOCITY else MINIMUM_VELOCITY } else { val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR // dismiss to the left in LTR locales, to the right in RTL if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY } } else { sign(requestedVelocity) * max(MINIMUM_VELOCITY, abs(requestedVelocity)) } } private fun viewClosestToLeftEdge(): Boolean { var rect = Rect() view.getBoundsOnScreen(rect) return rect.centerX() < view.resources.displayMetrics.widthPixels / 2f } companion object { private const val TAG = "ScreenshotAnimationController" private const val MINIMUM_VELOCITY = 1.5f // pixels per second private const val MINIMUM_VELOCITY = 1.5f // pixels per millisecond 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 private const val ACTION_REVEAL_DELAY_MS: Long = 200 val SCREENSHOT_DISMISSAL_SPRING = DesktopExperienceFlags.DesktopExperienceFlag( Flags::screenshotDismissalSpring, /* shouldOverrideByDevOption= */ true, Flags.FLAG_SCREENSHOT_DISMISSAL_SPRING, ) } } Loading
packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -726,6 +726,16 @@ flag { bug: "362720389" } flag { name: "screenshot_dismissal_spring" namespace: "systemui" description: "Use a spring animator for screenshot dismissal, fade out after const distance" bug: "412986001" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "screenshot_policy_split_and_desktop_mode" namespace: "systemui" Loading
packages/SystemUI/res/values/dimens.xml +2 −1 Original line number Diff line number Diff line Loading @@ -473,7 +473,8 @@ <!-- Dimensions related to screenshots --> <!-- Fade out the screenshot UI over this distance when dismissing --> <dimen name="dismissal_max_distance_fadeout">250dp</dimen> <dimen name="screenshot_crop_handle_thickness">3dp</dimen> <dimen name="long_screenshot_action_bar_top_margin">4dp</dimen> Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +32 −12 Original line number Diff line number Diff line Loading @@ -33,12 +33,14 @@ import android.view.View import android.view.ViewTreeObserver import android.view.WindowInsets import android.view.WindowManager import android.window.DesktopExperienceFlags import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.appcompat.content.res.AppCompatResources import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags import com.android.systemui.Flags.screenshotAnnounceLiveRegion import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R Loading Loading @@ -187,6 +189,14 @@ constructor( return } event?.let { logger.log(it, 0, packageName) } if (SCREENSHOT_DISMISSAL_SPRING.isTrue()) { animationController.startDismissal(velocity) { isDismissing = false callbacks?.onDismiss() } isDismissing = true } else { val animator = animationController.getSwipeDismissAnimation(velocity) animator.addListener( object : AnimatorListenerAdapter() { Loading @@ -202,6 +212,7 @@ constructor( ) animator.start() } } fun prepareScrollingTransition( response: ScrollCaptureResponse, Loading Loading @@ -362,6 +373,15 @@ constructor( ) } companion object { val SCREENSHOT_DISMISSAL_SPRING = DesktopExperienceFlags.DesktopExperienceFlag( Flags::screenshotDismissalSpring, /* shouldOverrideByDevOption= */ true, Flags.FLAG_SCREENSHOT_DISMISSAL_SPRING, ) } @AssistedFactory interface Factory { fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy Loading
packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +61 −4 Original line number Diff line number Diff line Loading @@ -31,8 +31,13 @@ import android.util.MathUtils import android.view.View import android.view.animation.AnimationUtils import android.widget.ImageView import android.window.DesktopExperienceFlags import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.internal.dynamicanimation.animation.DynamicAnimation import com.android.internal.dynamicanimation.animation.SpringAnimation import com.android.internal.dynamicanimation.animation.SpringForce import com.android.systemui.Flags import com.android.systemui.res.R import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel Loading @@ -45,6 +50,7 @@ class ScreenshotAnimationController( private val viewModel: ScreenshotViewModel, ) { private var animator: Animator? = null private var dismissalSpring: SpringAnimation? = null private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview) private val scrollingScrim = view.requireViewById<ImageView>((R.id.screenshot_scrolling_scrim)) private val scrollTransitionPreview = Loading Loading @@ -132,6 +138,7 @@ class ScreenshotAnimationController( fun fadeForSharedTransition() { animator?.cancel() dismissalSpring?.cancel() val fadeAnimator = ValueAnimator.ofFloat(1f, 0f) fadeAnimator.addUpdateListener { for (view in fadeUI) { Loading Loading @@ -214,6 +221,7 @@ class ScreenshotAnimationController( } fun restoreUI() { dismissalSpring?.cancel() animator?.cancel() for (view in fadeUI) { view.alpha = 1f Loading @@ -229,6 +237,35 @@ class ScreenshotAnimationController( return animator } fun startDismissal(requestedVelocity: Float?, onEnd: () -> Unit) { dismissalSpring?.cancel() animator?.cancel() val velocity = getAdjustedVelocity(requestedVelocity) val maxDistanceFadeout = view.resources.getDimensionPixelOffset(R.dimen.dismissal_max_distance_fadeout).toFloat() val startingTranslation = view.translationX // Set the spring target to be a bit past the max translation distance val target = startingTranslation + maxDistanceFadeout * sign(velocity) * 1.2f dismissalSpring = SpringAnimation(view, DynamicAnimation.TRANSLATION_X, target).apply { setStartVelocity(velocity * 1000) setStartValue(startingTranslation) spring?.setStiffness(SpringForce.STIFFNESS_LOW) spring?.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) } dismissalSpring?.addUpdateListener { _, value, _ -> view.alpha = MathUtils.lerp(1f, 0f, abs(value - startingTranslation) / maxDistanceFadeout) } dismissalSpring?.addEndListener { _, _, _, _ -> onEnd.invoke() } dismissalSpring?.start() } fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator { animator?.cancel() val velocity = getAdjustedVelocity(requestedVelocity) Loading @@ -247,6 +284,7 @@ class ScreenshotAnimationController( view.translationX = it.animatedValue as Float view.alpha = 1f - it.animatedFraction } animator.duration = ((abs(distance / velocity))).toLong() animator.doOnStart { viewModel.setIsAnimating(true) } animator.doOnEnd { viewModel.setIsAnimating(false) } Loading @@ -257,6 +295,7 @@ class ScreenshotAnimationController( fun cancel() { animator?.cancel() dismissalSpring?.cancel() } private fun getActionsAnimator(): Animator { Loading Loading @@ -325,21 +364,39 @@ class ScreenshotAnimationController( private fun getAdjustedVelocity(requestedVelocity: Float?): Float { return if (requestedVelocity == null || abs(requestedVelocity) < .005f) { val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR if (SCREENSHOT_DISMISSAL_SPRING.isTrue) { if (viewClosestToLeftEdge()) -MINIMUM_VELOCITY else MINIMUM_VELOCITY } else { val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR // dismiss to the left in LTR locales, to the right in RTL if (isLTR) -MINIMUM_VELOCITY else MINIMUM_VELOCITY } } else { sign(requestedVelocity) * max(MINIMUM_VELOCITY, abs(requestedVelocity)) } } private fun viewClosestToLeftEdge(): Boolean { var rect = Rect() view.getBoundsOnScreen(rect) return rect.centerX() < view.resources.displayMetrics.widthPixels / 2f } companion object { private const val TAG = "ScreenshotAnimationController" private const val MINIMUM_VELOCITY = 1.5f // pixels per second private const val MINIMUM_VELOCITY = 1.5f // pixels per millisecond 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 private const val ACTION_REVEAL_DELAY_MS: Long = 200 val SCREENSHOT_DISMISSAL_SPRING = DesktopExperienceFlags.DesktopExperienceFlag( Flags::screenshotDismissalSpring, /* shouldOverrideByDevOption= */ true, Flags.FLAG_SCREENSHOT_DISMISSAL_SPRING, ) } }