Add Haptic feedback to task dismiss animations.
Fix: 389640582 Test: Manual. Flag: com.android.launcher3.enable_expressive_dismiss_task_motion Change-Id: Icfbc875b4021136f612d8227f5cbbc1491e54801
This commit is contained in:
+25
@@ -26,10 +26,12 @@ import com.android.launcher3.Utilities.boundToRange
|
||||
import com.android.launcher3.Utilities.isRtl
|
||||
import com.android.launcher3.Utilities.mapToRange
|
||||
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.RecentsViewContainer
|
||||
import com.android.quickstep.views.TaskView
|
||||
import com.google.android.msdl.data.model.MSDLToken
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sign
|
||||
|
||||
@@ -53,6 +55,7 @@ CONTAINER : RecentsViewContainer {
|
||||
private var springAnimation: SpringAnimation? = null
|
||||
private var dismissLength: Int = 0
|
||||
private var verticalFactor: Int = 0
|
||||
private var hasDismissThresholdHapticRun = false
|
||||
private var initialDisplacement: Float = 0f
|
||||
|
||||
private fun canInterceptTouch(ev: MotionEvent): Boolean =
|
||||
@@ -159,9 +162,30 @@ CONTAINER : RecentsViewContainer {
|
||||
}
|
||||
recentsView.redrawLiveTile()
|
||||
}
|
||||
playDismissThresholdHaptic(displacement)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a haptic to alert the user they have passed the dismiss threshold.
|
||||
*
|
||||
* <p>Check within a range of the threshold value, as the drag event does not necessarily happen
|
||||
* at the exact threshold's displacement.
|
||||
*/
|
||||
private fun playDismissThresholdHaptic(displacement: Float) {
|
||||
val dismissThreshold = (DISMISS_THRESHOLD_FRACTION * dismissLength * verticalFactor)
|
||||
val inHapticRange =
|
||||
displacement >= (dismissThreshold - DISMISS_THRESHOLD_HAPTIC_RANGE) &&
|
||||
displacement <= (dismissThreshold + DISMISS_THRESHOLD_HAPTIC_RANGE)
|
||||
if (!inHapticRange) {
|
||||
hasDismissThresholdHapticRun = false
|
||||
} else if (!hasDismissThresholdHapticRun) {
|
||||
MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
|
||||
.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
|
||||
hasDismissThresholdHapticRun = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDragEnd(velocity: Float) {
|
||||
val taskBeingDragged = taskBeingDragged ?: return
|
||||
|
||||
@@ -208,5 +232,6 @@ CONTAINER : RecentsViewContainer {
|
||||
|
||||
companion object {
|
||||
private const val DISMISS_THRESHOLD_FRACTION = 0.5f
|
||||
private const val DISMISS_THRESHOLD_HAPTIC_RANGE = 10f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.quickstep.views
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.VibrationAttributes
|
||||
import android.view.View
|
||||
import androidx.core.view.children
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat
|
||||
@@ -26,14 +27,18 @@ import androidx.dynamicanimation.animation.SpringForce
|
||||
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
|
||||
import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities.boundToRange
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector
|
||||
import com.android.launcher3.util.DynamicResource
|
||||
import com.android.launcher3.util.IntArray
|
||||
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.RUNNING_TASK_ATTACH_ALPHA
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData
|
||||
import com.google.android.msdl.data.model.MSDLToken
|
||||
import com.google.android.msdl.domain.InteractionProperties
|
||||
import java.util.function.BiConsumer
|
||||
import kotlin.math.abs
|
||||
|
||||
@@ -378,7 +383,7 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) {
|
||||
// negative displacement to positive displacement). We do not check for an exact value
|
||||
// to compare to, as the update listener does not necessarily hit every value (e.g. a
|
||||
// value of zero). Do not check again once it has started settling, as a spring can
|
||||
// bounce past the origin multiple times depending on the stifness and damping ratio.
|
||||
// bounce past the origin multiple times depending on the stiffness and damping ratio.
|
||||
if (startSettling) return@addUpdateListener
|
||||
if (lastPosition < 0 && value >= 0) {
|
||||
startSettling = true
|
||||
@@ -386,6 +391,7 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) {
|
||||
lastPosition = value
|
||||
if (startSettling) {
|
||||
neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
|
||||
playDismissSettlingHaptic(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +506,27 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a haptic as the dragged task view settles back into its rest state.
|
||||
*
|
||||
* <p>Haptic intensity is proportional to velocity.
|
||||
*/
|
||||
private fun playDismissSettlingHaptic(velocity: Float) {
|
||||
val maxDismissSettlingVelocity =
|
||||
recentsView.pagedOrientationHandler.getSecondaryDimension(recentsView)
|
||||
MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
|
||||
.playToken(
|
||||
MSDLToken.CANCEL,
|
||||
InteractionProperties.DynamicVibrationScale(
|
||||
boundToRange(velocity / maxDismissSettlingVelocity, 0f, 1f),
|
||||
VibrationAttributes.Builder()
|
||||
.setUsage(VibrationAttributes.USAGE_TOUCH)
|
||||
.setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
|
||||
.build(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TEMP_RECT = Rect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user