Skip to content
Snippets Groups Projects
Commit 1789b5c9 authored by Miranda Kephart's avatar Miranda Kephart Committed by Matt Casey
Browse files

Update shelf screenshot UI entrance animation

Bug: 332410356
Bug: 329659738
Test: manual (visual animation change)
Flag: ACONFIG com.android.systemui.screenshot_shelf_ui DEVELOPMENT

Change-Id: If525186a81d896b8b3420f478045912e2a3771aa
Merged-In: If525186a81d896b8b3420f478045912e2a3771aa
parent cb9c41e8
No related branches found
No related tags found
No related merge requests found
......@@ -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>
......@@ -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
......@@ -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) {}
......
......@@ -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
......@@ -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
}
)
this.animator = animator
return animator
}
entranceAnimation.play(fadeInAnimator).after(previewAnimator)
entranceAnimation.doOnStart {
for (child in staticUI) {
child.alpha = 0f
}
}
this.animator = entranceAnimation
return entranceAnimation
}
fun getSwipeReturnAnimation(): Animator {
......@@ -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))
}
......@@ -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
}
}
......@@ -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()
......
......@@ -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 {
......@@ -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 {
......@@ -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)
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment