Merge "Add a stickiness factor to Back arrow on entry" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
682209e24f
@@ -60,15 +60,21 @@ private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
|
||||
private const val MIN_DURATION_COMMITTED_ANIMATION = 80L
|
||||
private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L
|
||||
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
|
||||
private const val MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION = 80L
|
||||
private const val MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION = 160F
|
||||
private const val MIN_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION = 10F
|
||||
internal const val MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION = 100F
|
||||
private const val MIN_DURATION_FLING_ANIMATION = 160L
|
||||
|
||||
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 POP_ON_FLING_DELAY = 60L
|
||||
private const val POP_ON_FLING_SCALE = 2f
|
||||
private const val POP_ON_COMMITTED_SCALE = 3f
|
||||
private const val POP_ON_FLING_VELOCITY = 2f
|
||||
private const val POP_ON_COMMITTED_VELOCITY = 3f
|
||||
private const val POP_ON_ACTIVE_MAX_VELOCITY = 2.5f
|
||||
private const val POP_ON_ACTIVE_MIN_VELOCITY = 4.5f
|
||||
private const val POP_ON_INACTIVE_MAX_VELOCITY = -1.05f
|
||||
private const val POP_ON_INACTIVE_MIN_VELOCITY = -1.5f
|
||||
|
||||
internal val VIBRATE_ACTIVATED_EFFECT =
|
||||
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
|
||||
@@ -156,13 +162,22 @@ class BackPanelController internal constructor(
|
||||
|
||||
private var gestureEntryTime = 0L
|
||||
private var gestureInactiveTime = 0L
|
||||
private var gesturePastActiveThresholdWhileInactiveTime = 0L
|
||||
|
||||
private val elapsedTimeSinceInactive
|
||||
get() = SystemClock.uptimeMillis() - gestureInactiveTime
|
||||
private val elapsedTimeSinceEntry
|
||||
get() = SystemClock.uptimeMillis() - gestureEntryTime
|
||||
|
||||
|
||||
private var pastThresholdWhileEntryOrInactiveTime = 0L
|
||||
private var entryToActiveDelay = 0F
|
||||
private val entryToActiveDelayCalculation = {
|
||||
convertVelocityToAnimationFactor(
|
||||
valueOnFastVelocity = MIN_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION,
|
||||
valueOnSlowVelocity = MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION,
|
||||
)
|
||||
}
|
||||
|
||||
// Whether the current gesture has moved a sufficiently large amount,
|
||||
// so that we can unambiguously start showing the ENTRY animation
|
||||
private var hasPassedDragSlop = false
|
||||
@@ -306,7 +321,9 @@ class BackPanelController internal constructor(
|
||||
MotionEvent.ACTION_UP -> {
|
||||
when (currentState) {
|
||||
GestureState.ENTRY -> {
|
||||
if (isFlungAwayFromEdge(endX = event.x)) {
|
||||
if (isFlungAwayFromEdge(endX = event.x) ||
|
||||
previousXTranslation > params.staticTriggerThreshold
|
||||
) {
|
||||
updateArrowState(GestureState.ACTIVE)
|
||||
updateArrowState(GestureState.FLUNG)
|
||||
} else {
|
||||
@@ -394,12 +411,29 @@ class BackPanelController internal constructor(
|
||||
}
|
||||
|
||||
private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) {
|
||||
|
||||
val isWithinYActivationThreshold = xTranslation * 2 >= yTranslation
|
||||
|
||||
val isPastStaticThreshold = xTranslation > params.staticTriggerThreshold
|
||||
when (currentState) {
|
||||
GestureState.ENTRY -> {
|
||||
if (xTranslation > params.staticTriggerThreshold) {
|
||||
if (isPastThresholdToActive(
|
||||
isPastThreshold = isPastStaticThreshold,
|
||||
dynamicDelay = entryToActiveDelayCalculation
|
||||
)
|
||||
) {
|
||||
updateArrowState(GestureState.ACTIVE)
|
||||
}
|
||||
}
|
||||
GestureState.INACTIVE -> {
|
||||
val isPastDynamicReactivationThreshold =
|
||||
totalTouchDeltaInactive >= params.reactivationTriggerThreshold
|
||||
|
||||
if (isPastThresholdToActive(
|
||||
isPastThreshold = isPastStaticThreshold &&
|
||||
isPastDynamicReactivationThreshold &&
|
||||
isWithinYActivationThreshold,
|
||||
delay = MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION
|
||||
)
|
||||
) {
|
||||
updateArrowState(GestureState.ACTIVE)
|
||||
}
|
||||
}
|
||||
@@ -408,43 +442,12 @@ class BackPanelController internal constructor(
|
||||
totalTouchDeltaActive <= params.deactivationTriggerThreshold
|
||||
val isMinDurationElapsed =
|
||||
elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION
|
||||
|
||||
if (isMinDurationElapsed && (!isWithinYActivationThreshold ||
|
||||
isPastDynamicDeactivationThreshold)
|
||||
) {
|
||||
val isPastAllThresholds =
|
||||
!isWithinYActivationThreshold || isPastDynamicDeactivationThreshold
|
||||
if (isPastAllThresholds && isMinDurationElapsed) {
|
||||
updateArrowState(GestureState.INACTIVE)
|
||||
}
|
||||
}
|
||||
GestureState.INACTIVE -> {
|
||||
val isPastStaticThreshold =
|
||||
xTranslation > params.staticTriggerThreshold
|
||||
val isPastDynamicReactivationThreshold =
|
||||
totalTouchDeltaInactive >= params.reactivationTriggerThreshold
|
||||
val isPastAllThresholds = isPastStaticThreshold &&
|
||||
isPastDynamicReactivationThreshold &&
|
||||
isWithinYActivationThreshold
|
||||
val isPastAllThresholdsForFirstTime = isPastAllThresholds &&
|
||||
gesturePastActiveThresholdWhileInactiveTime == 0L
|
||||
|
||||
gesturePastActiveThresholdWhileInactiveTime = when {
|
||||
isPastAllThresholdsForFirstTime -> SystemClock.uptimeMillis()
|
||||
isPastAllThresholds -> gesturePastActiveThresholdWhileInactiveTime
|
||||
else -> 0L
|
||||
}
|
||||
|
||||
val elapsedTimePastAllThresholds =
|
||||
SystemClock.uptimeMillis() - gesturePastActiveThresholdWhileInactiveTime
|
||||
|
||||
val isPastMinimumInactiveToActiveDuration =
|
||||
elapsedTimePastAllThresholds > MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION
|
||||
|
||||
if (isPastAllThresholds && isPastMinimumInactiveToActiveDuration) {
|
||||
// The minimum duration adds the 'edge stickiness'
|
||||
// factor before pulling it off edge
|
||||
updateArrowState(GestureState.ACTIVE)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -672,6 +675,28 @@ class BackPanelController internal constructor(
|
||||
return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
|
||||
}
|
||||
|
||||
private fun isPastThresholdToActive(
|
||||
isPastThreshold: Boolean,
|
||||
delay: Float? = null,
|
||||
dynamicDelay: () -> Float = { delay ?: 0F }
|
||||
): Boolean {
|
||||
val resetValue = 0L
|
||||
val isPastThresholdForFirstTime = pastThresholdWhileEntryOrInactiveTime == resetValue
|
||||
|
||||
if (!isPastThreshold) {
|
||||
pastThresholdWhileEntryOrInactiveTime = resetValue
|
||||
return false
|
||||
}
|
||||
|
||||
if (isPastThresholdForFirstTime) {
|
||||
pastThresholdWhileEntryOrInactiveTime = SystemClock.uptimeMillis()
|
||||
entryToActiveDelay = dynamicDelay()
|
||||
}
|
||||
val timePastThreshold = SystemClock.uptimeMillis() - pastThresholdWhileEntryOrInactiveTime
|
||||
|
||||
return timePastThreshold > entryToActiveDelay
|
||||
}
|
||||
|
||||
private fun playWithBackgroundWidthAnimation(
|
||||
onEnd: DelayedOnAnimationEndListener,
|
||||
delay: Long = 0L
|
||||
@@ -886,33 +911,16 @@ class BackPanelController internal constructor(
|
||||
}
|
||||
GestureState.ACTIVE -> {
|
||||
previousXTranslationOnActiveOffset = previousXTranslation
|
||||
|
||||
updateRestingArrowDimens()
|
||||
|
||||
vibratorHelper.cancel()
|
||||
mainHandler.postDelayed(10L) {
|
||||
vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
|
||||
}
|
||||
|
||||
val minimumPop = 2f
|
||||
val maximumPop = 4.5f
|
||||
|
||||
when (previousState) {
|
||||
GestureState.ENTRY -> {
|
||||
val startingVelocity = convertVelocityToSpringStartingVelocity(
|
||||
valueOnFastVelocity = minimumPop,
|
||||
valueOnSlowVelocity = maximumPop,
|
||||
fastVelocityBound = 1f,
|
||||
slowVelocityBound = 0.5f,
|
||||
)
|
||||
mView.popOffEdge(startingVelocity)
|
||||
}
|
||||
GestureState.INACTIVE -> {
|
||||
mView.popOffEdge(maximumPop)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
val startingVelocity = convertVelocityToAnimationFactor(
|
||||
valueOnFastVelocity = POP_ON_ACTIVE_MAX_VELOCITY,
|
||||
valueOnSlowVelocity = POP_ON_ACTIVE_MIN_VELOCITY,
|
||||
)
|
||||
mView.popOffEdge(startingVelocity)
|
||||
}
|
||||
|
||||
GestureState.INACTIVE -> {
|
||||
@@ -925,9 +933,9 @@ class BackPanelController internal constructor(
|
||||
// so that gesture progress in this state is consistent regardless of entry
|
||||
totalTouchDeltaInactive = params.deactivationTriggerThreshold
|
||||
|
||||
val startingVelocity = convertVelocityToSpringStartingVelocity(
|
||||
valueOnFastVelocity = -1.05f,
|
||||
valueOnSlowVelocity = -1.50f
|
||||
val startingVelocity = convertVelocityToAnimationFactor(
|
||||
valueOnFastVelocity = POP_ON_INACTIVE_MAX_VELOCITY,
|
||||
valueOnSlowVelocity = POP_ON_INACTIVE_MIN_VELOCITY
|
||||
)
|
||||
mView.popOffEdge(startingVelocity)
|
||||
|
||||
@@ -935,7 +943,9 @@ class BackPanelController internal constructor(
|
||||
updateRestingArrowDimens()
|
||||
}
|
||||
GestureState.FLUNG -> {
|
||||
mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_SCALE) }
|
||||
mainHandler.postDelayed(POP_ON_FLING_DELAY) {
|
||||
mView.popScale(POP_ON_FLING_VELOCITY)
|
||||
}
|
||||
updateRestingArrowDimens()
|
||||
mainHandler.postDelayed(onEndSetCommittedStateListener.runnable,
|
||||
MIN_DURATION_FLING_ANIMATION)
|
||||
@@ -951,7 +961,7 @@ class BackPanelController internal constructor(
|
||||
mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
|
||||
MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
|
||||
} else {
|
||||
mView.popScale(POP_ON_COMMITTED_SCALE)
|
||||
mView.popScale(POP_ON_COMMITTED_VELOCITY)
|
||||
mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
|
||||
MIN_DURATION_COMMITTED_ANIMATION)
|
||||
}
|
||||
@@ -968,11 +978,11 @@ class BackPanelController internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertVelocityToSpringStartingVelocity(
|
||||
private fun convertVelocityToAnimationFactor(
|
||||
valueOnFastVelocity: Float,
|
||||
valueOnSlowVelocity: Float,
|
||||
fastVelocityBound: Float = 3f,
|
||||
slowVelocityBound: Float = 0f,
|
||||
fastVelocityBound: Float = 1f,
|
||||
slowVelocityBound: Float = 0.5f,
|
||||
): Float {
|
||||
val factor = velocityTracker?.run {
|
||||
computeCurrentVelocity(PX_PER_MS)
|
||||
|
||||
@@ -153,7 +153,7 @@ data class EdgePanelParams(private var resources: Resources) {
|
||||
horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
|
||||
scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
|
||||
scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
|
||||
horizontalTranslationSpring = createSpring(500f, 0.76f),
|
||||
horizontalTranslationSpring = createSpring(800f, 0.76f),
|
||||
verticalTranslationSpring = createSpring(30000f, 1f),
|
||||
scaleSpring = createSpring(120f, 0.8f),
|
||||
arrowDimens = ArrowDimens(
|
||||
@@ -205,8 +205,8 @@ data class EdgePanelParams(private var resources: Resources) {
|
||||
activeIndicator = BackIndicatorDimens(
|
||||
horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
|
||||
scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
|
||||
horizontalTranslationSpring = createSpring(1000f, 0.7f),
|
||||
scaleSpring = createSpring(450f, 0.39f),
|
||||
horizontalTranslationSpring = createSpring(1000f, 0.8f),
|
||||
scaleSpring = createSpring(325f, 0.55f),
|
||||
scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width),
|
||||
arrowDimens = ArrowDimens(
|
||||
length = getDimen(R.dimen.navigation_edge_active_arrow_length),
|
||||
@@ -253,7 +253,7 @@ data class EdgePanelParams(private var resources: Resources) {
|
||||
getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
|
||||
farCornerRadius =
|
||||
getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
|
||||
widthSpring = createSpring(400f, 0.65f),
|
||||
widthSpring = createSpring(650f, 1f),
|
||||
heightSpring = createSpring(1500f, 0.45f),
|
||||
farCornerRadiusSpring = createSpring(300f, 1f),
|
||||
edgeCornerRadiusSpring = createSpring(250f, 0.5f),
|
||||
|
||||
@@ -104,6 +104,9 @@ class BackPanelControllerTest : SysuiTestCase() {
|
||||
continueTouch(START_X + touchSlop.toFloat() + 1)
|
||||
// Move again to cross the back trigger threshold
|
||||
continueTouch(START_X + touchSlop + triggerThreshold + 1)
|
||||
// Wait threshold duration and hold touch past trigger threshold
|
||||
Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
|
||||
continueTouch(START_X + touchSlop + triggerThreshold + 1)
|
||||
|
||||
assertThat(mBackPanelController.currentState)
|
||||
.isEqualTo(BackPanelController.GestureState.ACTIVE)
|
||||
@@ -114,14 +117,22 @@ class BackPanelControllerTest : SysuiTestCase() {
|
||||
|
||||
finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
|
||||
assertThat(mBackPanelController.currentState)
|
||||
.isEqualTo(BackPanelController.GestureState.FLUNG)
|
||||
.isEqualTo(BackPanelController.GestureState.COMMITTED)
|
||||
verify(backCallback).triggerBack()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handlesBackCancelled() {
|
||||
startTouch()
|
||||
// Move once to cross the touch slop
|
||||
continueTouch(START_X + touchSlop.toFloat() + 1)
|
||||
// Move again to cross the back trigger threshold
|
||||
continueTouch(
|
||||
START_X + touchSlop + triggerThreshold -
|
||||
mBackPanelController.params.deactivationTriggerThreshold
|
||||
)
|
||||
// Wait threshold duration and hold touch before trigger threshold
|
||||
Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
|
||||
continueTouch(
|
||||
START_X + touchSlop + triggerThreshold -
|
||||
mBackPanelController.params.deactivationTriggerThreshold
|
||||
|
||||
Reference in New Issue
Block a user