Fixing animation player not overshooting spring animation; tuning springs

=> setCurrentPlayTime is bounded between [0, duration] by the animation framework
Instead using interpolator so that we can go outside the bounds

=> Tune spring stiffness and dampening for overview card dismiss animations

Bug: 154061408
Change-Id: Iaa31491fff499db916b36d9779ec159b8a89a2de
This commit is contained in:
Sunny Goyal 2020-04-17 12:05:21 -07:00 committed by Adam Cohen
parent af560750d0
commit 7f9e8e2d27
5 changed files with 54 additions and 65 deletions

View File

@ -199,13 +199,12 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
RecentsView.CONTENT_ALPHA, values);
case INDEX_RECENTS_TRANSLATE_X_ANIM:
// TODO: Do not assume motion across X axis for adjacent page
return new SpringAnimationBuilder<>(
mLauncher.getOverviewPanel(), ADJACENT_PAGE_OFFSET)
return new SpringAnimationBuilder(mLauncher)
.setMinimumVisibleChange(1f / mLauncher.getOverviewPanel().getWidth())
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
.build(mLauncher);
.build(mLauncher.getOverviewPanel(), ADJACENT_PAGE_OFFSET);
case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;

View File

@ -28,6 +28,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;
@ -185,13 +186,13 @@ public class StaggeredWorkspaceAnim {
ResourceProvider rp = DynamicResource.provider(v.getContext());
float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y)
ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
.setStiffness(stiffness)
.setDampingRatio(damping)
.setMinimumVisibleChange(1f)
.setEndValue(0)
.setStartVelocity(mVelocity)
.build(v.getContext());
.build(v, VIEW_TRANSLATE_Y);
springTransY.setStartDelay(startDelay);
mAnimators.play(springTransY);

View File

@ -125,11 +125,11 @@
<item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
<item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
<item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.5</item>
<item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">1500</item>
<item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.73</item>
<item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">800</item>
<item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.5</item>
<item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">1500</item>
<item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.73</item>
<item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">800</item>
<item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.8</item>
<item name="horizontal_spring_stiffness" type="dimen" format="float">400</item>

View File

@ -27,7 +27,6 @@ import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.FloatProperty;
import androidx.annotation.Nullable;
@ -64,19 +63,6 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL
return new AnimatorPlaybackController(anim, duration, childAnims);
}
private static final FloatProperty<ValueAnimator> CURRENT_PLAY_TIME =
new FloatProperty<ValueAnimator>("current-play-time") {
@Override
public void setValue(ValueAnimator animator, float v) {
animator.setCurrentPlayTime((long) v);
}
@Override
public Float get(ValueAnimator animator) {
return (float) animator.getCurrentPlayTime();
}
};
// Progress factor after which an animation is considered almost completed.
private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f;
@ -177,21 +163,22 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL
long springDuration = animationDuration;
for (Holder h : mChildAnimations) {
if ((h.springProperty.flags & springFlag) != 0) {
SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME)
.setStartValue(clampDuration(mCurrentFraction))
.setEndValue(goingToEnd ? h.anim.getDuration() : 0)
.setStartVelocity(scaledVelocity * h.anim.getDuration())
SpringAnimationBuilder s = new SpringAnimationBuilder(context)
.setStartValue(mCurrentFraction)
.setEndValue(goingToEnd ? 1 : 0)
.setStartVelocity(scaledVelocity)
.setMinimumVisibleChange(scaleInverse)
.setDampingRatio(h.springProperty.mDampingRatio)
.setStiffness(h.springProperty.mStiffness);
.setStiffness(h.springProperty.mStiffness)
.computeParams();
long expectedDurationL = s.build(context).getDuration();
long expectedDurationL = s.getDuration();
springDuration = Math.max(expectedDurationL, springDuration);
float expectedDuration = expectedDurationL;
h.setter = (a, l) ->
s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
h.anim.setInterpolator(LINEAR);
h.setter = (a, l) -> a.setCurrentFraction(
mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
h.anim.setInterpolator(s::getInterpolatedValue);
}
}

View File

@ -15,7 +15,9 @@
*/
package com.android.launcher3.anim;
import android.animation.ObjectAnimator;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.FloatProperty;
@ -28,10 +30,9 @@ import com.android.launcher3.util.DefaultDisplay;
* Utility class to build an object animator which follows the same path as a spring animation for
* an underdamped spring.
*/
public class SpringAnimationBuilder<T> extends FloatProperty<T> {
public class SpringAnimationBuilder {
private final T mTarget;
private final FloatProperty<T> mProperty;
private final Context mContext;
private float mStartValue;
private float mEndValue;
@ -64,27 +65,23 @@ public class SpringAnimationBuilder<T> extends FloatProperty<T> {
private double mValueThreshold;
private double mVelocityThreshold;
private float mCurrentTime = 0;
private float mDuration = 0;
public SpringAnimationBuilder(T target, FloatProperty<T> property) {
super("dynamic-spring-property");
mTarget = target;
mProperty = property;
mStartValue = mProperty.get(target);
public SpringAnimationBuilder(Context context) {
mContext = context;
}
public SpringAnimationBuilder<T> setEndValue(float value) {
public SpringAnimationBuilder setEndValue(float value) {
mEndValue = value;
return this;
}
public SpringAnimationBuilder<T> setStartValue(float value) {
public SpringAnimationBuilder setStartValue(float value) {
mStartValue = value;
return this;
}
public SpringAnimationBuilder<T> setValues(float... values) {
public SpringAnimationBuilder setValues(float... values) {
if (values.length > 1) {
mStartValue = values[0];
mEndValue = values[values.length - 1];
@ -94,7 +91,7 @@ public class SpringAnimationBuilder<T> extends FloatProperty<T> {
return this;
}
public SpringAnimationBuilder<T> setStiffness(
public SpringAnimationBuilder setStiffness(
@FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
if (stiffness <= 0) {
throw new IllegalArgumentException("Spring stiffness constant must be positive.");
@ -103,7 +100,7 @@ public class SpringAnimationBuilder<T> extends FloatProperty<T> {
return this;
}
public SpringAnimationBuilder<T> setDampingRatio(
public SpringAnimationBuilder setDampingRatio(
@FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
float dampingRatio) {
if (dampingRatio <= 0 || dampingRatio >= 1) {
@ -113,7 +110,7 @@ public class SpringAnimationBuilder<T> extends FloatProperty<T> {
return this;
}
public SpringAnimationBuilder<T> setMinimumVisibleChange(
public SpringAnimationBuilder setMinimumVisibleChange(
@FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) {
if (minimumVisibleChange <= 0) {
throw new IllegalArgumentException("Minimum visible change must be positive.");
@ -122,25 +119,21 @@ public class SpringAnimationBuilder<T> extends FloatProperty<T> {
return this;
}
public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) {
public SpringAnimationBuilder setStartVelocity(float startVelocity) {
mVelocity = startVelocity;
return this;
}
@Override
public void setValue(T object, float time) {
mCurrentTime = time;
mProperty.setValue(
object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue);
public float getInterpolatedValue(float fraction) {
return getValue(mDuration * fraction);
}
@Override
public Float get(T t) {
return mCurrentTime;
private float getValue(float time) {
return (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue;
}
public ObjectAnimator build(Context context) {
int singleFrameMs = DefaultDisplay.getSingleFrameMs(context);
public SpringAnimationBuilder computeParams() {
int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext);
double naturalFreq = Math.sqrt(mStiffness);
double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
@ -187,12 +180,21 @@ public class SpringAnimationBuilder<T> extends FloatProperty<T> {
}
} while (true);
mDuration = (float) duration;
return this;
}
long durationMs = (long) (1000.0 * duration);
ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
animator.addListener(AnimationSuccessListener.forRunnable(
() -> mProperty.setValue(mTarget, mEndValue)));
public long getDuration() {
return (long) (1000.0 * mDuration);
}
public <T> ValueAnimator build(T target, FloatProperty<T> property) {
computeParams();
ValueAnimator animator = ValueAnimator.ofFloat(0, mDuration);
animator.setDuration(getDuration()).setInterpolator(LINEAR);
animator.addUpdateListener(anim ->
property.set(target, getInterpolatedValue(anim.getAnimatedFraction())));
return animator;
}