From d2728f84d943ddadf77565e2f63764753a05afc0 Mon Sep 17 00:00:00 2001 From: Marvin Bernal Date: Wed, 15 Mar 2023 04:29:21 +0000 Subject: [PATCH] Back arrow animation polish for CANCEL and fling from INACTIVE states Bug: 273619032 Test: Manual Change-Id: I2b846277776e4c441e5852878538516f95ae775c --- packages/SystemUI/res/values/dimens.xml | 4 - .../gestural/BackPanelController.kt | 82 ++++++++++++------ .../navigationbar/gestural/EdgePanelParams.kt | 83 ++++++++++--------- .../gestural/BackPanelControllerTest.kt | 2 +- 4 files changed, 100 insertions(+), 71 deletions(-) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4d38541e2cc98..ab304aae39f98 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -43,8 +43,6 @@ 268dp 16dp - - 24dp 412dp @@ -58,8 +56,6 @@ 4dp - - 32dp 0.98 diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 80ed08c901af6..e7bb6dcab30fa 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -53,11 +53,14 @@ private const val ENABLE_FAILSAFE = true private const val PX_PER_SEC = 1000 private const val PX_PER_MS = 1 -internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L +internal const val MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION = 300L +private const val MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L private const val MIN_DURATION_CANCELLED_ANIMATION = 200L private const val MIN_DURATION_COMMITTED_ANIMATION = 120L private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L -private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L + +private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L +private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L private const val FAILSAFE_DELAY_MS = 350L private const val POP_ON_FLING_DELAY = 140L @@ -145,12 +148,12 @@ class BackPanelController internal constructor( private var startY = 0f private var startIsLeft: Boolean? = null - private var gestureSinceActionDown = 0L private var gestureEntryTime = 0L + private var gestureInactiveTime = 0L private var gestureActiveTime = 0L - private val elapsedTimeSinceActionDown - get() = SystemClock.uptimeMillis() - gestureSinceActionDown + private val elapsedTimeSinceInactive + get() = SystemClock.uptimeMillis() - gestureInactiveTime private val elapsedTimeSinceEntry get() = SystemClock.uptimeMillis() - gestureEntryTime @@ -158,6 +161,9 @@ class BackPanelController internal constructor( // so that we can unambiguously start showing the ENTRY animation private var hasPassedDragSlop = false + // Distance in pixels a drag can be considered for a fling event + private var minFlingDistance = 0 + private val failsafeRunnable = Runnable { onFailsafe() } internal enum class GestureState { @@ -235,6 +241,7 @@ class BackPanelController internal constructor( private fun updateConfiguration() { params.update(resources) mView.updateArrowPaint(params.arrowThickness) + minFlingDistance = ViewConfiguration.get(context).scaledTouchSlop * 3 } private val configurationListener = object : ConfigurationController.ConfigurationListener { @@ -268,7 +275,6 @@ class BackPanelController internal constructor( velocityTracker!!.addMovement(event) when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { - gestureSinceActionDown = SystemClock.uptimeMillis() cancelAllPendingAnimations() startX = event.x startY = event.y @@ -307,8 +313,22 @@ class BackPanelController internal constructor( } } GestureState.ACTIVE -> { - if (elapsedTimeSinceEntry < MIN_DURATION_CONSIDERED_AS_FLING) { + if (previousState == GestureState.ENTRY && + elapsedTimeSinceEntry + < MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING + ) { updateArrowState(GestureState.FLUNG) + } else if (previousState == GestureState.INACTIVE && + elapsedTimeSinceInactive + < MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING + ) { + // A delay is added to allow the background to transition back to ACTIVE + // since it was briefly in INACTIVE. Without this delay, setting it + // immediately to COMMITTED would result in the committed animation + // appearing like it was playing in INACTIVE. + mainHandler.postDelayed(MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION) { + updateArrowState(GestureState.COMMITTED) + } } else { updateArrowState(GestureState.COMMITTED) } @@ -376,7 +396,7 @@ class BackPanelController internal constructor( val isPastDynamicDeactivationThreshold = totalTouchDelta <= params.deactivationSwipeTriggerThreshold val isMinDurationElapsed = - elapsedTimeSinceActionDown > MIN_DURATION_ACTIVE_ANIMATION + elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION if (isMinDurationElapsed && (!isWithinYActivationThreshold || isPastDynamicDeactivationThreshold) @@ -470,8 +490,15 @@ class BackPanelController internal constructor( GestureState.GONE -> 0f } + val indicator = when (currentState) { + GestureState.ENTRY -> params.entryIndicator + GestureState.INACTIVE -> params.preThresholdIndicator + GestureState.ACTIVE -> params.activeIndicator + else -> params.preThresholdIndicator + } + strokeAlphaProgress?.let { progress -> - params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let { + indicator.arrowDimens.alphaSpring?.get(progress)?.takeIf { it.isNewState }?.let { mView.popArrowAlpha(0f, it.value) } } @@ -537,7 +564,8 @@ class BackPanelController internal constructor( backgroundHeightStretchAmount = params.heightInterpolator .getInterpolation(progress), backgroundAlphaStretchAmount = 1f, - arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value, + arrowAlphaStretchAmount = params.entryIndicator.arrowDimens + .alphaInterpolator?.get(progress)?.value ?: 0f, edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress), farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress), fullyStretchedDimens = params.preThresholdIndicator @@ -567,7 +595,8 @@ class BackPanelController internal constructor( backgroundHeightStretchAmount = params.heightInterpolator .getInterpolation(progress), backgroundAlphaStretchAmount = 1f, - arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value, + arrowAlphaStretchAmount = params.preThresholdIndicator.arrowDimens + .alphaInterpolator?.get(progress)?.value ?: 0f, edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress), farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress), fullyStretchedDimens = params.preThresholdIndicator @@ -599,19 +628,15 @@ class BackPanelController internal constructor( windowManager.addView(mView, layoutParams) } - private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run { - computeCurrentVelocity(PX_PER_SEC) - val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1) - velocity > velocityPxPerSecThreshold - } - private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean { - val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX - val isPastFlingVelocity = isDragAwayFromEdge( - velocityPxPerSecThreshold = - ViewConfiguration.get(context).scaledMinimumFlingVelocity) - return flingDistance > minDistanceConsideredForFling && isPastFlingVelocity + val flingVelocity = velocityTracker?.run { + computeCurrentVelocity(PX_PER_SEC) + xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1) + } ?: 0f + val isPastFlingVelocityThreshold = + flingVelocity > ViewConfiguration.get(context).scaledMinimumFlingVelocity + return flingDistance > minFlingDistance && isPastFlingVelocityThreshold } private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) { @@ -664,7 +689,6 @@ class BackPanelController internal constructor( mView.setSpring( arrowLength = params.entryIndicator.arrowDimens.lengthSpring, arrowHeight = params.entryIndicator.arrowDimens.heightSpring, - arrowAlpha = params.entryIndicator.arrowDimens.alphaSpring, scale = params.entryIndicator.scaleSpring, verticalTranslation = params.entryIndicator.verticalTranslationSpring, horizontalTranslation = params.entryIndicator.horizontalTranslationSpring, @@ -725,6 +749,7 @@ class BackPanelController internal constructor( arrowLength = params.committedIndicator.arrowDimens.lengthSpring, arrowHeight = params.committedIndicator.arrowDimens.heightSpring, scale = params.committedIndicator.scaleSpring, + backgroundAlpha = params.committedIndicator.backgroundDimens.alphaSpring, backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring, backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring, backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens @@ -733,6 +758,10 @@ class BackPanelController internal constructor( .farCornerRadiusSpring, ) } + GestureState.CANCELLED -> { + mView.setSpring( + backgroundAlpha = params.cancelledIndicator.backgroundDimens.alphaSpring) + } else -> {} } @@ -864,6 +893,7 @@ class BackPanelController internal constructor( } GestureState.INACTIVE -> { + gestureInactiveTime = SystemClock.uptimeMillis() // Typically entering INACTIVE means // totalTouchDelta <= deactivationSwipeTriggerThreshold @@ -900,9 +930,9 @@ class BackPanelController internal constructor( val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry) playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay) - params.arrowStrokeAlphaSpring.get(0f).takeIf { it.isNewState }?.let { - mView.popArrowAlpha(0f, it.value) - } + val springForceOnCancelled = params.cancelledIndicator + .arrowDimens.alphaSpring?.get(0f)?.value + mView.popArrowAlpha(0f, springForceOnCancelled) mainHandler.postDelayed(10L) { vibratorHelper.cancel() } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index d46333a50c8d1..3dc6d2f8464dc 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -12,9 +12,10 @@ data class EdgePanelParams(private var resources: Resources) { val length: Float? = 0f, val height: Float? = 0f, val alpha: Float = 0f, - var alphaSpring: SpringForce? = null, val heightSpring: SpringForce? = null, val lengthSpring: SpringForce? = null, + var alphaSpring: Step? = null, + var alphaInterpolator: Step? = null ) data class BackgroundDimens( @@ -61,11 +62,6 @@ data class EdgePanelParams(private var resources: Resources) { private set var arrowThickness: Float = 0f private set - lateinit var arrowStrokeAlphaSpring: Step - private set - lateinit var arrowStrokeAlphaInterpolator: Step - private set - // The closest to y var minArrowYPosition: Int = 0 private set @@ -81,13 +77,6 @@ data class EdgePanelParams(private var resources: Resources) { var swipeProgressThreshold: Float = 0f private set - // The minimum delta needed to change direction / stop triggering back - var minDeltaForSwitch: Int = 0 - private set - - var minDragToStartAnimation: Float = 0f - private set - lateinit var entryWidthInterpolator: PathInterpolator private set lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator @@ -133,23 +122,17 @@ data class EdgePanelParams(private var resources: Resources) { deactivationSwipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold) swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold) - minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch) - minDragToStartAnimation = - getDimen(R.dimen.navigation_edge_action_min_distance_to_start_animation) entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f) entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f) - activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f) + activeWidthInterpolator = PathInterpolator(.7f, .06f, .34f, .97f) arrowAngleInterpolator = entryWidthInterpolator translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f) farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f) edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f) heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f) - val showArrowOnProgressValue = .23f - val showArrowOnProgressValueFactor = 1.05f - - val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f) + val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.76f) val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f) val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f) val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f) @@ -157,6 +140,8 @@ data class EdgePanelParams(private var resources: Resources) { val flungCommittedWidthSpring = createSpring(10000f, 1f) val flungCommittedHeightSpring = createSpring(10000f, 1f) + val entryIndicatorAlphaThreshold = .23f + val entryIndicatorAlphaFactor = 1.05f entryIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin), scale = getDimenFloat(R.dimen.navigation_edge_entry_scale), @@ -168,9 +153,20 @@ data class EdgePanelParams(private var resources: Resources) { length = getDimen(R.dimen.navigation_edge_entry_arrow_length), height = getDimen(R.dimen.navigation_edge_entry_arrow_height), alpha = 0f, - alphaSpring = createSpring(200f, 1f), lengthSpring = createSpring(600f, 0.4f), heightSpring = createSpring(600f, 0.4f), + alphaSpring = Step( + threshold = entryIndicatorAlphaThreshold, + factor = entryIndicatorAlphaFactor, + postThreshold = createSpring(200f, 1f), + preThreshold = createSpring(2000f, 0.6f) + ), + alphaInterpolator = Step( + threshold = entryIndicatorAlphaThreshold, + factor = entryIndicatorAlphaFactor, + postThreshold = 1f, + preThreshold = 0f + ) ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -186,6 +182,20 @@ data class EdgePanelParams(private var resources: Resources) { ) ) + val preThresholdAndActiveIndicatorAlphaThreshold = .355f + val preThresholdAndActiveIndicatorAlphaFactor = 1.05f + val preThresholdAndActiveAlphaSpring = Step( + threshold = preThresholdAndActiveIndicatorAlphaThreshold, + factor = preThresholdAndActiveIndicatorAlphaFactor, + postThreshold = createSpring(180f, 0.9f), + preThreshold = createSpring(2000f, 0.6f) + ) + val preThresholdAndActiveAlphaSpringInterpolator = Step( + threshold = preThresholdAndActiveIndicatorAlphaThreshold, + factor = preThresholdAndActiveIndicatorAlphaFactor, + postThreshold = 1f, + preThreshold = 0f + ) activeIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), @@ -197,6 +207,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 1f, lengthSpring = activeCommittedArrowLengthSpring, heightSpring = activeCommittedArrowHeightSpring, + alphaSpring = preThresholdAndActiveAlphaSpring, + alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -204,7 +216,7 @@ data class EdgePanelParams(private var resources: Resources) { height = getDimen(R.dimen.navigation_edge_active_background_height), edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners), farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners), - widthSpring = createSpring(375f, 0.675f), + widthSpring = createSpring(650f, 0.75f), heightSpring = createSpring(10000f, 1f), edgeCornerRadiusSpring = createSpring(600f, 0.36f), farCornerRadiusSpring = createSpring(2500f, 0.855f), @@ -223,6 +235,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 1f, lengthSpring = createSpring(100f, 0.6f), heightSpring = createSpring(100f, 0.6f), + alphaSpring = preThresholdAndActiveAlphaSpring, + alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -255,6 +269,7 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = flungCommittedHeightSpring, edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring, farCornerRadiusSpring = flungCommittedFarCornerSpring, + alphaSpring = createSpring(1100f, 1f), ), scale = 0.85f, scaleSpring = createSpring(1150f, 1f), @@ -276,7 +291,11 @@ data class EdgePanelParams(private var resources: Resources) { ) cancelledIndicator = entryIndicator.copy( - backgroundDimens = entryIndicator.backgroundDimens.copy(width = 0f) + backgroundDimens = entryIndicator.backgroundDimens.copy( + width = 0f, + alpha = 0f, + alphaSpring = createSpring(450f, 1f) + ) ) fullyStretchedIndicator = BackIndicatorDimens( @@ -306,22 +325,6 @@ data class EdgePanelParams(private var resources: Resources) { farCornerRadiusSpring = null, ) ) - - arrowStrokeAlphaInterpolator = Step( - threshold = showArrowOnProgressValue, - factor = showArrowOnProgressValueFactor, - postThreshold = 1f, - preThreshold = 0f - ) - - entryIndicator.arrowDimens.alphaSpring?.let { alphaSpring -> - arrowStrokeAlphaSpring = Step( - threshold = showArrowOnProgressValue, - factor = showArrowOnProgressValueFactor, - postThreshold = alphaSpring, - preThreshold = SpringForce().setStiffness(2000f).setDampingRatio(1f) - ) - } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt index bc31a0ec81a67..8e32f81b193fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt @@ -127,7 +127,7 @@ class BackPanelControllerTest : SysuiTestCase() { mBackPanelController.params.deactivationSwipeTriggerThreshold ) clearInvocations(backCallback) - Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION) + Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION) // Move in the opposite direction to cross the deactivation threshold and cancel back continueTouch(START_X)