• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.animation;
18 
19 import android.animation.Animator;
20 import android.util.DisplayMetrics;
21 import android.util.Log;
22 import android.view.ViewPropertyAnimator;
23 import android.view.animation.Interpolator;
24 import android.view.animation.PathInterpolator;
25 
26 import javax.inject.Inject;
27 
28 /**
29  * Utility class to calculate general fling animation when the finger is released.
30  */
31 public class FlingAnimationUtils {
32 
33     private static final String TAG = "FlingAnimationUtils";
34 
35     private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
36     private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f;
37     private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
38     private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
39     private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
40     private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
41     private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
42 
43     private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f;
44     private final float mSpeedUpFactor;
45     private final float mY2;
46 
47     private float mMinVelocityPxPerSecond;
48     private float mMaxLengthSeconds;
49     private float mHighVelocityPxPerSecond;
50     private float mLinearOutSlowInX2;
51 
52     private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
53     private PathInterpolator mInterpolator;
54     private float mCachedStartGradient = -1;
55     private float mCachedVelocityFactor = -1;
56 
FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds)57     public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds) {
58         this(displayMetrics, maxLengthSeconds, 0.0f);
59     }
60 
61     /**
62      * @param maxLengthSeconds the longest duration an animation can become in seconds
63      * @param speedUpFactor    a factor from 0 to 1 how much the slow down should be shifted towards
64      *                         the end of the animation. 0 means it's at the beginning and no
65      *                         acceleration will take place.
66      */
FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds, float speedUpFactor)67     public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds,
68             float speedUpFactor) {
69         this(displayMetrics, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
70     }
71 
72     /**
73      * @param maxLengthSeconds the longest duration an animation can become in seconds
74      * @param speedUpFactor    a factor from 0 to 1 how much the slow down should be shifted towards
75      *                         the end of the animation. 0 means it's at the beginning and no
76      *                         acceleration will take place.
77      * @param x2               the x value to take for the second point of the bezier spline. If a
78      *                         value below 0 is provided, the value is automatically calculated.
79      * @param y2               the y value to take for the second point of the bezier spline
80      */
FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds, float speedUpFactor, float x2, float y2)81     public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds,
82             float speedUpFactor, float x2, float y2) {
83         mMaxLengthSeconds = maxLengthSeconds;
84         mSpeedUpFactor = speedUpFactor;
85         if (x2 < 0) {
86             mLinearOutSlowInX2 = interpolate(LINEAR_OUT_SLOW_IN_X2,
87                     LINEAR_OUT_SLOW_IN_X2_MAX,
88                     mSpeedUpFactor);
89         } else {
90             mLinearOutSlowInX2 = x2;
91         }
92         mY2 = y2;
93 
94         mMinVelocityPxPerSecond = MIN_VELOCITY_DP_PER_SECOND * displayMetrics.density;
95         mHighVelocityPxPerSecond = HIGH_VELOCITY_DP_PER_SECOND * displayMetrics.density;
96     }
97 
98     /**
99      * Applies the interpolator and length to the animator, such that the fling animation is
100      * consistent with the finger motion.
101      *
102      * @param animator  the animator to apply
103      * @param currValue the current value
104      * @param endValue  the end value of the animator
105      * @param velocity  the current velocity of the motion
106      */
apply(Animator animator, float currValue, float endValue, float velocity)107     public void apply(Animator animator, float currValue, float endValue, float velocity) {
108         apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
109     }
110 
111     /**
112      * Applies the interpolator and length to the animator, such that the fling animation is
113      * consistent with the finger motion.
114      *
115      * @param animator  the animator to apply
116      * @param currValue the current value
117      * @param endValue  the end value of the animator
118      * @param velocity  the current velocity of the motion
119      */
apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity)120     public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
121             float velocity) {
122         apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
123     }
124 
125     /**
126      * Applies the interpolator and length to the animator, such that the fling animation is
127      * consistent with the finger motion.
128      *
129      * @param animator    the animator to apply
130      * @param currValue   the current value
131      * @param endValue    the end value of the animator
132      * @param velocity    the current velocity of the motion
133      * @param maxDistance the maximum distance for this interaction; the maximum animation length
134      *                    gets multiplied by the ratio between the actual distance and this value
135      */
apply(Animator animator, float currValue, float endValue, float velocity, float maxDistance)136     public void apply(Animator animator, float currValue, float endValue, float velocity,
137             float maxDistance) {
138         AnimatorProperties properties = getProperties(currValue, endValue, velocity,
139                 maxDistance);
140         animator.setDuration(properties.mDuration);
141         animator.setInterpolator(properties.mInterpolator);
142     }
143 
144     /**
145      * Applies the interpolator and length to the animator, such that the fling animation is
146      * consistent with the finger motion.
147      *
148      * @param animator    the animator to apply
149      * @param currValue   the current value
150      * @param endValue    the end value of the animator
151      * @param velocity    the current velocity of the motion
152      * @param maxDistance the maximum distance for this interaction; the maximum animation length
153      *                    gets multiplied by the ratio between the actual distance and this value
154      */
apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)155     public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
156             float velocity, float maxDistance) {
157         AnimatorProperties properties = getProperties(currValue, endValue, velocity,
158                 maxDistance);
159         animator.setDuration(properties.mDuration);
160         animator.setInterpolator(properties.mInterpolator);
161     }
162 
getProperties(float currValue, float endValue, float velocity, float maxDistance)163     private AnimatorProperties getProperties(float currValue,
164             float endValue, float velocity, float maxDistance) {
165         float maxLengthSeconds = (float) (mMaxLengthSeconds
166                 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
167         float diff = Math.abs(endValue - currValue);
168         float velAbs = Math.abs(velocity);
169         float velocityFactor = mSpeedUpFactor == 0.0f
170                 ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f);
171         float startGradient = interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT,
172                 mY2 / mLinearOutSlowInX2, velocityFactor);
173         float durationSeconds = startGradient * diff / velAbs;
174         Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor);
175         if (durationSeconds <= maxLengthSeconds) {
176             mAnimatorProperties.mInterpolator = slowInInterpolator;
177         } else if (velAbs >= mMinVelocityPxPerSecond) {
178 
179             // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
180             durationSeconds = maxLengthSeconds;
181             VelocityInterpolator velocityInterpolator = new VelocityInterpolator(
182                     durationSeconds, velAbs, diff);
183             InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
184                     velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN);
185             mAnimatorProperties.mInterpolator = superInterpolator;
186         } else {
187 
188             // Just use a normal interpolator which doesn't take the velocity into account.
189             durationSeconds = maxLengthSeconds;
190             mAnimatorProperties.mInterpolator = Interpolators.FAST_OUT_SLOW_IN;
191         }
192         mAnimatorProperties.mDuration = (long) (durationSeconds * 1000);
193         return mAnimatorProperties;
194     }
195 
getInterpolator(float startGradient, float velocityFactor)196     private Interpolator getInterpolator(float startGradient, float velocityFactor) {
197         if (Float.isNaN(velocityFactor)) {
198             Log.e(TAG, "Invalid velocity factor", new Throwable());
199             return Interpolators.LINEAR_OUT_SLOW_IN;
200         }
201         if (startGradient != mCachedStartGradient
202                 || velocityFactor != mCachedVelocityFactor) {
203             float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
204             float x1 = speedup;
205             float y1 = speedup * startGradient;
206             float x2 = mLinearOutSlowInX2;
207             float y2 = mY2;
208             try {
209                 mInterpolator = new PathInterpolator(x1, y1, x2, y2);
210             } catch (IllegalArgumentException e) {
211                 throw new IllegalArgumentException("Illegal path with "
212                         + "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e);
213             }
214             mCachedStartGradient = startGradient;
215             mCachedVelocityFactor = velocityFactor;
216         }
217         return mInterpolator;
218     }
219 
220     /**
221      * Applies the interpolator and length to the animator, such that the fling animation is
222      * consistent with the finger motion for the case when the animation is making something
223      * disappear.
224      *
225      * @param animator    the animator to apply
226      * @param currValue   the current value
227      * @param endValue    the end value of the animator
228      * @param velocity    the current velocity of the motion
229      * @param maxDistance the maximum distance for this interaction; the maximum animation length
230      *                    gets multiplied by the ratio between the actual distance and this value
231      */
applyDismissing(Animator animator, float currValue, float endValue, float velocity, float maxDistance)232     public void applyDismissing(Animator animator, float currValue, float endValue,
233             float velocity, float maxDistance) {
234         AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
235                 maxDistance);
236         animator.setDuration(properties.mDuration);
237         animator.setInterpolator(properties.mInterpolator);
238     }
239 
240     /**
241      * Applies the interpolator and length to the animator, such that the fling animation is
242      * consistent with the finger motion for the case when the animation is making something
243      * disappear.
244      *
245      * @param animator    the animator to apply
246      * @param currValue   the current value
247      * @param endValue    the end value of the animator
248      * @param velocity    the current velocity of the motion
249      * @param maxDistance the maximum distance for this interaction; the maximum animation length
250      *                    gets multiplied by the ratio between the actual distance and this value
251      */
applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)252     public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
253             float velocity, float maxDistance) {
254         AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
255                 maxDistance);
256         animator.setDuration(properties.mDuration);
257         animator.setInterpolator(properties.mInterpolator);
258     }
259 
getDismissingProperties(float currValue, float endValue, float velocity, float maxDistance)260     private AnimatorProperties getDismissingProperties(float currValue, float endValue,
261             float velocity, float maxDistance) {
262         float maxLengthSeconds = (float) (mMaxLengthSeconds
263                 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
264         float diff = Math.abs(endValue - currValue);
265         float velAbs = Math.abs(velocity);
266         float y2 = calculateLinearOutFasterInY2(velAbs);
267 
268         float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
269         Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
270         float durationSeconds = startGradient * diff / velAbs;
271         if (durationSeconds <= maxLengthSeconds) {
272             mAnimatorProperties.mInterpolator = mLinearOutFasterIn;
273         } else if (velAbs >= mMinVelocityPxPerSecond) {
274 
275             // Cross fade between linear-out-faster-in and linear interpolator with current
276             // velocity.
277             durationSeconds = maxLengthSeconds;
278             VelocityInterpolator velocityInterpolator = new VelocityInterpolator(
279                     durationSeconds, velAbs, diff);
280             InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
281                     velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN);
282             mAnimatorProperties.mInterpolator = superInterpolator;
283         } else {
284 
285             // Just use a normal interpolator which doesn't take the velocity into account.
286             durationSeconds = maxLengthSeconds;
287             mAnimatorProperties.mInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
288         }
289         mAnimatorProperties.mDuration = (long) (durationSeconds * 1000);
290         return mAnimatorProperties;
291     }
292 
293     /**
294      * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
295      * velocity. The faster the velocity, the more "linear" the interpolator gets.
296      *
297      * @param velocity the velocity of the gesture.
298      * @return the y2 control point for a cubic bezier path interpolator
299      */
calculateLinearOutFasterInY2(float velocity)300     private float calculateLinearOutFasterInY2(float velocity) {
301         float t = (velocity - mMinVelocityPxPerSecond)
302                 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
303         t = Math.max(0, Math.min(1, t));
304         return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
305     }
306 
307     /**
308      * @return the minimum velocity a gesture needs to have to be considered a fling
309      */
getMinVelocityPxPerSecond()310     public float getMinVelocityPxPerSecond() {
311         return mMinVelocityPxPerSecond;
312     }
313 
314     /**
315      * @return a velocity considered fast
316      */
getHighVelocityPxPerSecond()317     public float getHighVelocityPxPerSecond() {
318         return mHighVelocityPxPerSecond;
319     }
320 
321     /**
322      * An interpolator which interpolates two interpolators with an interpolator.
323      */
324     private static final class InterpolatorInterpolator implements Interpolator {
325 
326         private Interpolator mInterpolator1;
327         private Interpolator mInterpolator2;
328         private Interpolator mCrossfader;
329 
InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader)330         InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
331                 Interpolator crossfader) {
332             mInterpolator1 = interpolator1;
333             mInterpolator2 = interpolator2;
334             mCrossfader = crossfader;
335         }
336 
337         @Override
getInterpolation(float input)338         public float getInterpolation(float input) {
339             float t = mCrossfader.getInterpolation(input);
340             return (1 - t) * mInterpolator1.getInterpolation(input)
341                     + t * mInterpolator2.getInterpolation(input);
342         }
343     }
344 
345     /**
346      * An interpolator which interpolates with a fixed velocity.
347      */
348     private static final class VelocityInterpolator implements Interpolator {
349 
350         private float mDurationSeconds;
351         private float mVelocity;
352         private float mDiff;
353 
VelocityInterpolator(float durationSeconds, float velocity, float diff)354         private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
355             mDurationSeconds = durationSeconds;
356             mVelocity = velocity;
357             mDiff = diff;
358         }
359 
360         @Override
getInterpolation(float input)361         public float getInterpolation(float input) {
362             float time = input * mDurationSeconds;
363             return time * mVelocity / mDiff;
364         }
365     }
366 
367     private static class AnimatorProperties {
368         Interpolator mInterpolator;
369         long mDuration;
370     }
371 
372     /** Builder for {@link #FlingAnimationUtils}. */
373     public static class Builder {
374         private final DisplayMetrics mDisplayMetrics;
375         float mMaxLengthSeconds;
376         float mSpeedUpFactor;
377         float mX2;
378         float mY2;
379 
380         @Inject
Builder(DisplayMetrics displayMetrics)381         public Builder(DisplayMetrics displayMetrics) {
382             mDisplayMetrics = displayMetrics;
383             reset();
384         }
385 
386         /** Sets the longest duration an animation can become in seconds. */
setMaxLengthSeconds(float maxLengthSeconds)387         public Builder setMaxLengthSeconds(float maxLengthSeconds) {
388             mMaxLengthSeconds = maxLengthSeconds;
389             return this;
390         }
391 
392         /**
393          * Sets the factor for how much the slow down should be shifted towards the end of the
394          * animation.
395          */
setSpeedUpFactor(float speedUpFactor)396         public Builder setSpeedUpFactor(float speedUpFactor) {
397             mSpeedUpFactor = speedUpFactor;
398             return this;
399         }
400 
401         /** Sets the x value to take for the second point of the bezier spline. */
setX2(float x2)402         public Builder setX2(float x2) {
403             mX2 = x2;
404             return this;
405         }
406 
407         /** Sets the y value to take for the second point of the bezier spline. */
setY2(float y2)408         public Builder setY2(float y2) {
409             mY2 = y2;
410             return this;
411         }
412 
413         /** Resets all parameters of the builder. */
reset()414         public Builder reset() {
415             mMaxLengthSeconds = 0;
416             mSpeedUpFactor = 0.0f;
417             mX2 = -1.0f;
418             mY2 = 1.0f;
419 
420             return this;
421         }
422 
423         /** Builds {@link #FlingAnimationUtils}. */
build()424         public FlingAnimationUtils build() {
425             return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor,
426                     mX2, mY2);
427         }
428     }
429 
interpolate(float start, float end, float amount)430     private static float interpolate(float start, float end, float amount) {
431         return start * (1.0f - amount) + end * amount;
432     }
433 }
434