Swipe up overshoot always plays

Instead of using an OvershootInterpolator, we adjust the end progress to > 1 and
add a second interpolator to settle back to 1. That way, even if the animation runs
starting very late, e.g. 1.0, it still has room to overshoot. We use this same
OvershootParams class to calculate an overshoot for a blocked long fling as well.

Bug: 109709720
Change-Id: I43152237e4350f93e7c462c22e68d09d05c1dd57
This commit is contained in:
Tony 2018-06-24 22:47:04 -07:00
parent 563bc3d1fa
commit cc3755da6e
3 changed files with 133 additions and 36 deletions

View File

@ -20,17 +20,22 @@ import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.Interpolators.OvershootParams;
import com.android.launcher3.uioverrides.PortraitStatesTouchController;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@ -89,7 +94,9 @@ public class LongSwipeHelper {
}
public void end(float velocity, boolean isFling, Runnable callback) {
float velocityPxPerMs = velocity / 1000;
long duration = MAX_SWIPE_DURATION;
Interpolator interpolator = DEACCEL;
final float currentFraction = mAnimator.getProgressFraction();
final boolean toAllApps;
@ -107,6 +114,17 @@ public class LongSwipeHelper {
long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
if (blockedFling && !toAllApps) {
Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
duration = (overshoot.duration + duration)
* LauncherAnimUtils.blockedFlingDurationFactor(0);
duration = Utilities.boundToRange(duration, MIN_OVERSHOOT_DURATION,
MAX_SWIPE_DURATION);
interpolator = overshoot.interpolator;
endProgress = overshoot.end;
}
} else {
toAllApps = velocity < 0;
endProgress = toAllApps ? 1 : 0;
@ -119,18 +137,16 @@ public class LongSwipeHelper {
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity));
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
if (blockedFling && !toAllApps) {
duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
}
final boolean finalIsFling = isFling;
mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
ValueAnimator animator = mAnimator.getAnimationPlayer();
animator.setDuration(duration).setInterpolator(DEACCEL);
animator.setDuration(duration).setInterpolator(interpolator);
animator.setFloatValues(currentFraction, endProgress);
animator.start();
}

View File

@ -21,6 +21,7 @@ import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
@ -163,6 +164,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
public static final long MAX_SWIPE_DURATION = 350;
public static final long MIN_SWIPE_DURATION = 80;
public static final long MIN_OVERSHOOT_DURATION = 120;
public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
private static final float SWIPE_DURATION_MULTIPLIER =
@ -498,7 +500,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
// Start the window animation without waiting for launcher.
animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR);
animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
true /* goingToHome */);
}
private void shiftAnimationDestinationForQuickscrub() {
@ -699,36 +702,46 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
float velocityPxPerMs = endVelocity / 1000;
long duration = MAX_SWIPE_DURATION;
final float endShift;
float currentShift = mCurrentShift.value;
final boolean goingToHome;
float endShift;
final float startShift;
final Interpolator interpolator;
Interpolator interpolator = DEACCEL;
if (!isFling) {
endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted ? 1 : 0;
long expectedDuration = Math.abs(Math.round((endShift - mCurrentShift.value)
goingToHome = currentShift >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted;
endShift = goingToHome ? 1 : 0;
long expectedDuration = Math.abs(Math.round((endShift - currentShift)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
startShift = mCurrentShift.value;
interpolator = DEACCEL;
startShift = currentShift;
interpolator = goingToHome ? OVERSHOOT_1_2 : DEACCEL;
} else {
endShift = endVelocity < 0 ? 1 : 0;
interpolator = endVelocity < 0
? Interpolators.overshootInterpolatorForVelocity(velocityPxPerMs, 2f)
: DEACCEL;
goingToHome = endVelocity < 0;
endShift = goingToHome ? 1 : 0;
startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
* SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
float distanceToTravel = (endShift - mCurrentShift.value) * mTransitionDragLength;
if (goingToHome) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
endShift = overshoot.end;
interpolator = overshoot.interpolator;
duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
MAX_SWIPE_DURATION);
} else {
float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
startShift = Utilities.boundToRange(mCurrentShift.value - velocityPxPerMs
* SINGLE_FRAME_MS / (mTransitionDragLength), 0, 1);
}
animateToProgress(startShift, endShift, duration, interpolator);
animateToProgress(startShift, endShift, duration, interpolator, goingToHome);
}
private void doLogGesture(boolean toLauncher) {
@ -754,14 +767,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float start, float end, long duration,
Interpolator interpolator) {
Interpolator interpolator, boolean goingToHome) {
mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
interpolator));
interpolator, goingToHome));
}
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator) {
mIsGoingToHome = Float.compare(end, 1) == 0;
Interpolator interpolator, boolean goingToHome) {
mIsGoingToHome = goingToHome;
ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
anim.setInterpolator(interpolator);
anim.addListener(new AnimationSuccessListener() {

View File

@ -16,7 +16,10 @@
package com.android.launcher3.anim;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import android.graphics.Path;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@ -45,6 +48,8 @@ public class Interpolators {
public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
@ -141,17 +146,12 @@ public class Interpolators {
return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC;
}
public static Interpolator overshootInterpolatorForVelocity(float velocity) {
return overshootInterpolatorForVelocity(velocity, 1f);
}
/**
* Create an OvershootInterpolator with tension directly related to the velocity (in px/ms).
* @param velocity The start velocity of the animation we want to overshoot.
* @param dampFactor An optional factor to reduce the amount of tension (how far we overshoot).
*/
public static Interpolator overshootInterpolatorForVelocity(float velocity, float dampFactor) {
return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f) / dampFactor);
public static Interpolator overshootInterpolatorForVelocity(float velocity) {
return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f));
}
/**
@ -183,4 +183,72 @@ public class Interpolators {
float upperBound) {
return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
}
/**
* Computes parameters necessary for an overshoot effect.
*/
public static class OvershootParams {
public Interpolator interpolator;
public float start;
public float end;
public long duration;
/**
* Given the input params, sets OvershootParams variables to be used by the caller.
* @param startProgress The progress from 0 to 1 that the overshoot starts from.
* @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should
* either be equal to startProgress or endProgress, depending on if we want to
* overshoot immediately or only once we reach the end).
* @param endProgress The final progress from 0 to 1 that we will settle to.
* @param velocityPxPerMs The initial velocity that causes this overshoot.
* @param totalDistancePx The distance against which progress is calculated.
*/
public OvershootParams(float startProgress, float overshootPastProgress,
float endProgress, float velocityPxPerMs, int totalDistancePx) {
velocityPxPerMs = Math.abs(velocityPxPerMs);
start = startProgress;
int startPx = (int) (start * totalDistancePx);
// Overshoot by about half a frame.
float overshootBy = velocityPxPerMs * SINGLE_FRAME_MS / totalDistancePx / 2;
overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
end = overshootPastProgress + overshootBy;
int endPx = (int) (end * totalDistancePx);
int overshootDistance = endPx - startPx;
// Calculate deceleration necessary to reach overshoot distance.
// Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance
// 0 = v^2 + 2ad (velocityFinal == 0)
// a = v^2 / -2d
float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance);
// Calculate time necessary to reach peak of overshoot.
// Formula: acceleration = velocity / time
// time = velocity / acceleration
duration = (long) (velocityPxPerMs / decelerationPxPerMs);
// Now that we're at the top of the overshoot, need to settle back to endProgress.
float settleDistance = end - endProgress;
int settleDistancePx = (int) (settleDistance * totalDistancePx);
// Calculate time necessary for the settle.
// Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2
// d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top)
// t = sqrt(2d/a)
// Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually
// have acceleration to halfway then deceleration the rest. So the formula becomes:
// t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel)
long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 2;
// How much of the animation to devote to playing the overshoot (the rest is for settle).
float overshootFraction = (float) duration / (duration + settleDuration);
duration += settleDuration;
// Finally, create the interpolator, composed of two interpolators: an overshoot, which
// reaches end > 1, and then a settle to endProgress.
Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction);
// The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction
// such that final progress is endProgress. For example, if we overshot to 1.1 but want
// to end at 1, we need to map to 1/1.1.
Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress(
ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1);
interpolator = t -> t <= overshootFraction
? overshoot.getInterpolation(t)
: settle.getInterpolation(t);
}
}
}