diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt index 98737a5568..7a3cdb765b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt @@ -19,6 +19,7 @@ import android.content.Context import android.view.MotionEvent import androidx.dynamicanimation.animation.SpringAnimation import com.android.app.animation.Interpolators.DECELERATE +import com.android.app.animation.Interpolators.LINEAR import com.android.launcher3.AbstractFloatingView import com.android.launcher3.R import com.android.launcher3.Utilities.EDGE_NAV_BAR @@ -29,6 +30,7 @@ import com.android.launcher3.touch.SingleAxisSwipeDetector import com.android.launcher3.util.MSDLPlayerWrapper import com.android.launcher3.util.TouchController import com.android.quickstep.views.RecentsView +import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY import com.android.quickstep.views.RecentsViewContainer import com.android.quickstep.views.TaskView import com.google.android.msdl.data.model.MSDLToken @@ -57,6 +59,7 @@ CONTAINER : RecentsViewContainer { private var verticalFactor: Int = 0 private var hasDismissThresholdHapticRun = false private var initialDisplacement: Float = 0f + private var recentsScaleAnimation: SpringAnimation? = null private fun canInterceptTouch(ev: MotionEvent): Boolean = when { @@ -98,6 +101,7 @@ CONTAINER : RecentsViewContainer { private fun onActionDown(ev: MotionEvent): Boolean { springAnimation?.cancel() + recentsScaleAnimation?.cancel() if (!canInterceptTouch(ev)) { return false } @@ -162,6 +166,8 @@ CONTAINER : RecentsViewContainer { } recentsView.redrawLiveTile() } + val dismissFraction = displacement / (dismissLength * verticalFactor).toFloat() + RECENTS_SCALE_PROPERTY.setValue(recentsView, getRecentsScale(dismissFraction)) playDismissThresholdHaptic(displacement) return true } @@ -216,6 +222,10 @@ CONTAINER : RecentsViewContainer { if (isDismissing) (dismissLength * verticalFactor).toFloat() else 0f ) } + recentsScaleAnimation = + recentsView.animateRecentsScale(RECENTS_SCALE_DEFAULT).addEndListener { _, _, _, _ -> + recentsScaleAnimation = null + } } // Returns if the current task being dragged is towards "positive" (e.g. dismissal). @@ -230,8 +240,54 @@ CONTAINER : RecentsViewContainer { springAnimation = null } + private fun getRecentsScale(dismissFraction: Float): Float { + return when { + // Do not scale recents when dragging below origin. + dismissFraction <= 0 -> { + RECENTS_SCALE_DEFAULT + } + // Initially scale recents as the drag begins, up to the first threshold. + dismissFraction < RECENTS_SCALE_FIRST_THRESHOLD_FRACTION -> { + mapToRange( + dismissFraction, + 0f, + RECENTS_SCALE_FIRST_THRESHOLD_FRACTION, + RECENTS_SCALE_DEFAULT, + RECENTS_SCALE_ON_DISMISS_CANCEL, + LINEAR, + ) + } + // Keep scale consistent until dragging to the dismiss threshold. + dismissFraction < RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION -> { + RECENTS_SCALE_ON_DISMISS_CANCEL + } + // Scale beyond the dismiss threshold again, to indicate dismiss will occur on release. + dismissFraction < RECENTS_SCALE_SECOND_THRESHOLD_FRACTION -> { + mapToRange( + dismissFraction, + RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION, + RECENTS_SCALE_SECOND_THRESHOLD_FRACTION, + RECENTS_SCALE_ON_DISMISS_CANCEL, + RECENTS_SCALE_ON_DISMISS_SUCCESS, + LINEAR, + ) + } + // Keep scale beyond the dismiss threshold scaling consistent. + else -> { + RECENTS_SCALE_ON_DISMISS_SUCCESS + } + } + } + companion object { private const val DISMISS_THRESHOLD_FRACTION = 0.5f private const val DISMISS_THRESHOLD_HAPTIC_RANGE = 10f + + private const val RECENTS_SCALE_ON_DISMISS_CANCEL = 0.9875f + private const val RECENTS_SCALE_ON_DISMISS_SUCCESS = 0.975f + private const val RECENTS_SCALE_DEFAULT = 1f + private const val RECENTS_SCALE_FIRST_THRESHOLD_FRACTION = 0.2f + private const val RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION = 0.5f + private const val RECENTS_SCALE_SECOND_THRESHOLD_FRACTION = 0.575f } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 424271a4c5..d37fe9a450 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -6950,6 +6950,13 @@ public abstract class RecentsView< isDismissing, detector, dismissLength, onEndRunnable); } + /** + * Animates RecentsView's scale to the provided value, using spring animations. + */ + public SpringAnimation animateRecentsScale(float scale) { + return mUtils.animateRecentsScale(scale); + } + public interface TaskLaunchListener { void onTaskLaunched(); } diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt index d18a5f7fe8..5ee661e926 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt +++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt @@ -35,6 +35,7 @@ import com.android.launcher3.util.MSDLPlayerWrapper import com.android.quickstep.util.GroupTask import com.android.quickstep.util.TaskGridNavHelper import com.android.quickstep.util.isExternalDisplay +import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA import com.android.systemui.shared.recents.model.ThumbnailData import com.google.android.msdl.data.model.MSDLToken @@ -362,7 +363,6 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) { addNeighboringSpringAnimationsForDismissCancel( draggedTaskView, draggedTaskViewSpringAnimation, - recentsView.pageCount, ) } return draggedTaskViewSpringAnimation @@ -371,7 +371,6 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) { private fun addNeighboringSpringAnimationsForDismissCancel( draggedTaskView: TaskView, draggedTaskViewSpringAnimation: SpringAnimation, - taskCount: Int, ) { // Empty spring animation exists for conditional start, and to drive neighboring springs. val neighborsToSettle = @@ -559,10 +558,39 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) { ) } + /** Animates RecentsView's scale to the provided value, using spring animations. */ + fun animateRecentsScale(scale: Float): SpringAnimation { + val resourceProvider = DynamicResource.provider(recentsView.mContainer) + val dampingRatio = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio) + val stiffness = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_stiffness) + + // Spring which sets the Recents scale on update. This is needed, as the SpringAnimation + // struggles to animate small values like changing recents scale from 0.9 to 1. So + // we animate over a larger range (e.g. 900 to 1000) and convert back to the required value. + // (This is instead of converting RECENTS_SCALE_PROPERTY to a FloatPropertyCompat and + // animating it directly via springs.) + val initialRecentsScaleSpringValue = + RECENTS_SCALE_SPRING_MULTIPLIER * RECENTS_SCALE_PROPERTY.get(recentsView) + return SpringAnimation(FloatValueHolder(initialRecentsScaleSpringValue)) + .setSpring( + SpringForce(initialRecentsScaleSpringValue) + .setDampingRatio(dampingRatio) + .setStiffness(stiffness) + ) + .addUpdateListener { _, value, _ -> + RECENTS_SCALE_PROPERTY.setValue( + recentsView, + value / RECENTS_SCALE_SPRING_MULTIPLIER, + ) + } + .apply { animateToFinalPosition(RECENTS_SCALE_SPRING_MULTIPLIER * scale) } + } + companion object { val TEMP_RECT = Rect() // The additional damping to apply to tasks further from the dismissed task. - const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f + private const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f + private const val RECENTS_SCALE_SPRING_MULTIPLIER = 1000f } }