/*
|
* Copyright (C) 2014 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License
|
*/
|
|
package com.android.systemui.statusbar;
|
|
import android.animation.Animator;
|
import android.content.Context;
|
import android.util.Log;
|
import android.view.ViewPropertyAnimator;
|
import android.view.animation.Interpolator;
|
import android.view.animation.PathInterpolator;
|
|
import com.android.systemui.Interpolators;
|
import com.android.systemui.statusbar.notification.NotificationUtils;
|
import com.android.systemui.statusbar.phone.StatusBar;
|
|
/**
|
* Utility class to calculate general fling animation when the finger is released.
|
*/
|
public class FlingAnimationUtils {
|
|
private static final String TAG = "FlingAnimationUtils";
|
|
private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
|
private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f;
|
private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
|
private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
|
private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
|
private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
|
private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
|
|
private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f;
|
private final float mSpeedUpFactor;
|
private final float mY2;
|
|
private float mMinVelocityPxPerSecond;
|
private float mMaxLengthSeconds;
|
private float mHighVelocityPxPerSecond;
|
private float mLinearOutSlowInX2;
|
|
private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
|
private PathInterpolator mInterpolator;
|
private float mCachedStartGradient = -1;
|
private float mCachedVelocityFactor = -1;
|
|
public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
|
this(ctx, maxLengthSeconds, 0.0f);
|
}
|
|
/**
|
* @param maxLengthSeconds the longest duration an animation can become in seconds
|
* @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
|
* the end of the animation. 0 means it's at the beginning and no
|
* acceleration will take place.
|
*/
|
public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) {
|
this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
|
}
|
|
/**
|
* @param maxLengthSeconds the longest duration an animation can become in seconds
|
* @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
|
* the end of the animation. 0 means it's at the beginning and no
|
* acceleration will take place.
|
* @param x2 the x value to take for the second point of the bezier spline. If a value below 0
|
* is provided, the value is automatically calculated.
|
* @param y2 the y value to take for the second point of the bezier spline
|
*/
|
public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2,
|
float y2) {
|
mMaxLengthSeconds = maxLengthSeconds;
|
mSpeedUpFactor = speedUpFactor;
|
if (x2 < 0) {
|
mLinearOutSlowInX2 = NotificationUtils.interpolate(LINEAR_OUT_SLOW_IN_X2,
|
LINEAR_OUT_SLOW_IN_X2_MAX,
|
mSpeedUpFactor);
|
} else {
|
mLinearOutSlowInX2 = x2;
|
}
|
mY2 = y2;
|
|
mMinVelocityPxPerSecond
|
= MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
|
mHighVelocityPxPerSecond
|
= HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
|
}
|
|
/**
|
* Applies the interpolator and length to the animator, such that the fling animation is
|
* consistent with the finger motion.
|
*
|
* @param animator the animator to apply
|
* @param currValue the current value
|
* @param endValue the end value of the animator
|
* @param velocity the current velocity of the motion
|
*/
|
public void apply(Animator animator, float currValue, float endValue, float velocity) {
|
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
|
}
|
|
/**
|
* Applies the interpolator and length to the animator, such that the fling animation is
|
* consistent with the finger motion.
|
*
|
* @param animator the animator to apply
|
* @param currValue the current value
|
* @param endValue the end value of the animator
|
* @param velocity the current velocity of the motion
|
*/
|
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
|
float velocity) {
|
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
|
}
|
|
/**
|
* Applies the interpolator and length to the animator, such that the fling animation is
|
* consistent with the finger motion.
|
*
|
* @param animator the animator to apply
|
* @param currValue the current value
|
* @param endValue the end value of the animator
|
* @param velocity the current velocity of the motion
|
* @param maxDistance the maximum distance for this interaction; the maximum animation length
|
* gets multiplied by the ratio between the actual distance and this value
|
*/
|
public void apply(Animator animator, float currValue, float endValue, float velocity,
|
float maxDistance) {
|
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
|
maxDistance);
|
animator.setDuration(properties.duration);
|
animator.setInterpolator(properties.interpolator);
|
}
|
|
/**
|
* Applies the interpolator and length to the animator, such that the fling animation is
|
* consistent with the finger motion.
|
*
|
* @param animator the animator to apply
|
* @param currValue the current value
|
* @param endValue the end value of the animator
|
* @param velocity the current velocity of the motion
|
* @param maxDistance the maximum distance for this interaction; the maximum animation length
|
* gets multiplied by the ratio between the actual distance and this value
|
*/
|
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
|
float velocity, float maxDistance) {
|
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
|
maxDistance);
|
animator.setDuration(properties.duration);
|
animator.setInterpolator(properties.interpolator);
|
}
|
|
private AnimatorProperties getProperties(float currValue,
|
float endValue, float velocity, float maxDistance) {
|
float maxLengthSeconds = (float) (mMaxLengthSeconds
|
* Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
|
float diff = Math.abs(endValue - currValue);
|
float velAbs = Math.abs(velocity);
|
float velocityFactor = mSpeedUpFactor == 0.0f
|
? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f);
|
float startGradient = NotificationUtils.interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT,
|
mY2 / mLinearOutSlowInX2, velocityFactor);
|
float durationSeconds = startGradient * diff / velAbs;
|
Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor);
|
if (durationSeconds <= maxLengthSeconds) {
|
mAnimatorProperties.interpolator = slowInInterpolator;
|
} else if (velAbs >= mMinVelocityPxPerSecond) {
|
|
// Cross fade between fast-out-slow-in and linear interpolator with current velocity.
|
durationSeconds = maxLengthSeconds;
|
VelocityInterpolator velocityInterpolator
|
= new VelocityInterpolator(durationSeconds, velAbs, diff);
|
InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
|
velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN);
|
mAnimatorProperties.interpolator = superInterpolator;
|
} else {
|
|
// Just use a normal interpolator which doesn't take the velocity into account.
|
durationSeconds = maxLengthSeconds;
|
mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN;
|
}
|
mAnimatorProperties.duration = (long) (durationSeconds * 1000);
|
return mAnimatorProperties;
|
}
|
|
private Interpolator getInterpolator(float startGradient, float velocityFactor) {
|
if (Float.isNaN(velocityFactor)) {
|
Log.e(TAG, "Invalid velocity factor", new Throwable());
|
return Interpolators.LINEAR_OUT_SLOW_IN;
|
}
|
if (startGradient != mCachedStartGradient
|
|| velocityFactor != mCachedVelocityFactor) {
|
float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
|
float x1 = speedup;
|
float y1 = speedup * startGradient;
|
float x2 = mLinearOutSlowInX2;
|
float y2 = mY2;
|
try {
|
mInterpolator = new PathInterpolator(x1, y1, x2, y2);
|
} catch (IllegalArgumentException e) {
|
throw new IllegalArgumentException("Illegal path with "
|
+ "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e);
|
}
|
mCachedStartGradient = startGradient;
|
mCachedVelocityFactor = velocityFactor;
|
}
|
return mInterpolator;
|
}
|
|
/**
|
* Applies the interpolator and length to the animator, such that the fling animation is
|
* consistent with the finger motion for the case when the animation is making something
|
* disappear.
|
*
|
* @param animator the animator to apply
|
* @param currValue the current value
|
* @param endValue the end value of the animator
|
* @param velocity the current velocity of the motion
|
* @param maxDistance the maximum distance for this interaction; the maximum animation length
|
* gets multiplied by the ratio between the actual distance and this value
|
*/
|
public void applyDismissing(Animator animator, float currValue, float endValue,
|
float velocity, float maxDistance) {
|
AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
|
maxDistance);
|
animator.setDuration(properties.duration);
|
animator.setInterpolator(properties.interpolator);
|
}
|
|
/**
|
* Applies the interpolator and length to the animator, such that the fling animation is
|
* consistent with the finger motion for the case when the animation is making something
|
* disappear.
|
*
|
* @param animator the animator to apply
|
* @param currValue the current value
|
* @param endValue the end value of the animator
|
* @param velocity the current velocity of the motion
|
* @param maxDistance the maximum distance for this interaction; the maximum animation length
|
* gets multiplied by the ratio between the actual distance and this value
|
*/
|
public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
|
float velocity, float maxDistance) {
|
AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
|
maxDistance);
|
animator.setDuration(properties.duration);
|
animator.setInterpolator(properties.interpolator);
|
}
|
|
private AnimatorProperties getDismissingProperties(float currValue, float endValue,
|
float velocity, float maxDistance) {
|
float maxLengthSeconds = (float) (mMaxLengthSeconds
|
* Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
|
float diff = Math.abs(endValue - currValue);
|
float velAbs = Math.abs(velocity);
|
float y2 = calculateLinearOutFasterInY2(velAbs);
|
|
float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
|
Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
|
float durationSeconds = startGradient * diff / velAbs;
|
if (durationSeconds <= maxLengthSeconds) {
|
mAnimatorProperties.interpolator = mLinearOutFasterIn;
|
} else if (velAbs >= mMinVelocityPxPerSecond) {
|
|
// Cross fade between linear-out-faster-in and linear interpolator with current
|
// velocity.
|
durationSeconds = maxLengthSeconds;
|
VelocityInterpolator velocityInterpolator
|
= new VelocityInterpolator(durationSeconds, velAbs, diff);
|
InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
|
velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN);
|
mAnimatorProperties.interpolator = superInterpolator;
|
} else {
|
|
// Just use a normal interpolator which doesn't take the velocity into account.
|
durationSeconds = maxLengthSeconds;
|
mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN;
|
}
|
mAnimatorProperties.duration = (long) (durationSeconds * 1000);
|
return mAnimatorProperties;
|
}
|
|
/**
|
* Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
|
* velocity. The faster the velocity, the more "linear" the interpolator gets.
|
*
|
* @param velocity the velocity of the gesture.
|
* @return the y2 control point for a cubic bezier path interpolator
|
*/
|
private float calculateLinearOutFasterInY2(float velocity) {
|
float t = (velocity - mMinVelocityPxPerSecond)
|
/ (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
|
t = Math.max(0, Math.min(1, t));
|
return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
|
}
|
|
/**
|
* @return the minimum velocity a gesture needs to have to be considered a fling
|
*/
|
public float getMinVelocityPxPerSecond() {
|
return mMinVelocityPxPerSecond;
|
}
|
|
/**
|
* An interpolator which interpolates two interpolators with an interpolator.
|
*/
|
private static final class InterpolatorInterpolator implements Interpolator {
|
|
private Interpolator mInterpolator1;
|
private Interpolator mInterpolator2;
|
private Interpolator mCrossfader;
|
|
InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
|
Interpolator crossfader) {
|
mInterpolator1 = interpolator1;
|
mInterpolator2 = interpolator2;
|
mCrossfader = crossfader;
|
}
|
|
@Override
|
public float getInterpolation(float input) {
|
float t = mCrossfader.getInterpolation(input);
|
return (1 - t) * mInterpolator1.getInterpolation(input)
|
+ t * mInterpolator2.getInterpolation(input);
|
}
|
}
|
|
/**
|
* An interpolator which interpolates with a fixed velocity.
|
*/
|
private static final class VelocityInterpolator implements Interpolator {
|
|
private float mDurationSeconds;
|
private float mVelocity;
|
private float mDiff;
|
|
private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
|
mDurationSeconds = durationSeconds;
|
mVelocity = velocity;
|
mDiff = diff;
|
}
|
|
@Override
|
public float getInterpolation(float input) {
|
float time = input * mDurationSeconds;
|
return time * mVelocity / mDiff;
|
}
|
}
|
|
private static class AnimatorProperties {
|
Interpolator interpolator;
|
long duration;
|
}
|
|
}
|