• 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.systemui.statusbar;
18 
19 import android.animation.Animator;
20 import android.content.Context;
21 import android.view.ViewPropertyAnimator;
22 import android.view.animation.Interpolator;
23 import android.view.animation.PathInterpolator;
24 
25 import com.android.systemui.Interpolators;
26 
27 /**
28  * Utility class to calculate general fling animation when the finger is released.
29  */
30 public class FlingAnimationUtils {
31 
32     private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
33     private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
34     private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
35     private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
36     private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
37     private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
38 
39     /**
40      * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve
41      */
42     private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2;
43 
44     private Interpolator mLinearOutSlowIn;
45 
46     private float mMinVelocityPxPerSecond;
47     private float mMaxLengthSeconds;
48     private float mHighVelocityPxPerSecond;
49 
50     private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
51 
FlingAnimationUtils(Context ctx, float maxLengthSeconds)52     public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
53         mMaxLengthSeconds = maxLengthSeconds;
54         mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1);
55         mMinVelocityPxPerSecond
56                 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
57         mHighVelocityPxPerSecond
58                 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
59     }
60 
61     /**
62      * Applies the interpolator and length to the animator, such that the fling animation is
63      * consistent with the finger motion.
64      *
65      * @param animator the animator to apply
66      * @param currValue the current value
67      * @param endValue the end value of the animator
68      * @param velocity the current velocity of the motion
69      */
apply(Animator animator, float currValue, float endValue, float velocity)70     public void apply(Animator animator, float currValue, float endValue, float velocity) {
71         apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
72     }
73 
74     /**
75      * Applies the interpolator and length to the animator, such that the fling animation is
76      * consistent with the finger motion.
77      *
78      * @param animator the animator to apply
79      * @param currValue the current value
80      * @param endValue the end value of the animator
81      * @param velocity the current velocity of the motion
82      */
apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity)83     public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
84             float velocity) {
85         apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
86     }
87 
88     /**
89      * Applies the interpolator and length to the animator, such that the fling animation is
90      * consistent with the finger motion.
91      *
92      * @param animator the animator to apply
93      * @param currValue the current value
94      * @param endValue the end value of the animator
95      * @param velocity the current velocity of the motion
96      * @param maxDistance the maximum distance for this interaction; the maximum animation length
97      *                    gets multiplied by the ratio between the actual distance and this value
98      */
apply(Animator animator, float currValue, float endValue, float velocity, float maxDistance)99     public void apply(Animator animator, float currValue, float endValue, float velocity,
100             float maxDistance) {
101         AnimatorProperties properties = getProperties(currValue, endValue, velocity,
102                 maxDistance);
103         animator.setDuration(properties.duration);
104         animator.setInterpolator(properties.interpolator);
105     }
106 
107     /**
108      * Applies the interpolator and length to the animator, such that the fling animation is
109      * consistent with the finger motion.
110      *
111      * @param animator the animator to apply
112      * @param currValue the current value
113      * @param endValue the end value of the animator
114      * @param velocity the current velocity of the motion
115      * @param maxDistance the maximum distance for this interaction; the maximum animation length
116      *                    gets multiplied by the ratio between the actual distance and this value
117      */
apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)118     public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
119             float velocity, float maxDistance) {
120         AnimatorProperties properties = getProperties(currValue, endValue, velocity,
121                 maxDistance);
122         animator.setDuration(properties.duration);
123         animator.setInterpolator(properties.interpolator);
124     }
125 
getProperties(float currValue, float endValue, float velocity, float maxDistance)126     private AnimatorProperties getProperties(float currValue,
127             float endValue, float velocity, float maxDistance) {
128         float maxLengthSeconds = (float) (mMaxLengthSeconds
129                 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
130         float diff = Math.abs(endValue - currValue);
131         float velAbs = Math.abs(velocity);
132         float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs;
133         if (durationSeconds <= maxLengthSeconds) {
134             mAnimatorProperties.interpolator = mLinearOutSlowIn;
135         } else if (velAbs >= mMinVelocityPxPerSecond) {
136 
137             // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
138             durationSeconds = maxLengthSeconds;
139             VelocityInterpolator velocityInterpolator
140                     = new VelocityInterpolator(durationSeconds, velAbs, diff);
141             InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
142                     velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn);
143             mAnimatorProperties.interpolator = superInterpolator;
144         } else {
145 
146             // Just use a normal interpolator which doesn't take the velocity into account.
147             durationSeconds = maxLengthSeconds;
148             mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN;
149         }
150         mAnimatorProperties.duration = (long) (durationSeconds * 1000);
151         return mAnimatorProperties;
152     }
153 
154     /**
155      * Applies the interpolator and length to the animator, such that the fling animation is
156      * consistent with the finger motion for the case when the animation is making something
157      * disappear.
158      *
159      * @param animator the animator to apply
160      * @param currValue the current value
161      * @param endValue the end value of the animator
162      * @param velocity the current velocity of the motion
163      * @param maxDistance the maximum distance for this interaction; the maximum animation length
164      *                    gets multiplied by the ratio between the actual distance and this value
165      */
applyDismissing(Animator animator, float currValue, float endValue, float velocity, float maxDistance)166     public void applyDismissing(Animator animator, float currValue, float endValue,
167             float velocity, float maxDistance) {
168         AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
169                 maxDistance);
170         animator.setDuration(properties.duration);
171         animator.setInterpolator(properties.interpolator);
172     }
173 
174     /**
175      * Applies the interpolator and length to the animator, such that the fling animation is
176      * consistent with the finger motion for the case when the animation is making something
177      * disappear.
178      *
179      * @param animator the animator to apply
180      * @param currValue the current value
181      * @param endValue the end value of the animator
182      * @param velocity the current velocity of the motion
183      * @param maxDistance the maximum distance for this interaction; the maximum animation length
184      *                    gets multiplied by the ratio between the actual distance and this value
185      */
applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)186     public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
187             float velocity, float maxDistance) {
188         AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
189                 maxDistance);
190         animator.setDuration(properties.duration);
191         animator.setInterpolator(properties.interpolator);
192     }
193 
getDismissingProperties(float currValue, float endValue, float velocity, float maxDistance)194     private AnimatorProperties getDismissingProperties(float currValue, float endValue,
195             float velocity, float maxDistance) {
196         float maxLengthSeconds = (float) (mMaxLengthSeconds
197                 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
198         float diff = Math.abs(endValue - currValue);
199         float velAbs = Math.abs(velocity);
200         float y2 = calculateLinearOutFasterInY2(velAbs);
201 
202         float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
203         Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
204         float durationSeconds = startGradient * diff / velAbs;
205         if (durationSeconds <= maxLengthSeconds) {
206             mAnimatorProperties.interpolator = mLinearOutFasterIn;
207         } else if (velAbs >= mMinVelocityPxPerSecond) {
208 
209             // Cross fade between linear-out-faster-in and linear interpolator with current
210             // velocity.
211             durationSeconds = maxLengthSeconds;
212             VelocityInterpolator velocityInterpolator
213                     = new VelocityInterpolator(durationSeconds, velAbs, diff);
214             InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
215                     velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn);
216             mAnimatorProperties.interpolator = superInterpolator;
217         } else {
218 
219             // Just use a normal interpolator which doesn't take the velocity into account.
220             durationSeconds = maxLengthSeconds;
221             mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN;
222         }
223         mAnimatorProperties.duration = (long) (durationSeconds * 1000);
224         return mAnimatorProperties;
225     }
226 
227     /**
228      * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
229      * velocity. The faster the velocity, the more "linear" the interpolator gets.
230      *
231      * @param velocity the velocity of the gesture.
232      * @return the y2 control point for a cubic bezier path interpolator
233      */
calculateLinearOutFasterInY2(float velocity)234     private float calculateLinearOutFasterInY2(float velocity) {
235         float t = (velocity - mMinVelocityPxPerSecond)
236                 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
237         t = Math.max(0, Math.min(1, t));
238         return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
239     }
240 
241     /**
242      * @return the minimum velocity a gesture needs to have to be considered a fling
243      */
getMinVelocityPxPerSecond()244     public float getMinVelocityPxPerSecond() {
245         return mMinVelocityPxPerSecond;
246     }
247 
248     /**
249      * An interpolator which interpolates two interpolators with an interpolator.
250      */
251     private static final class InterpolatorInterpolator implements Interpolator {
252 
253         private Interpolator mInterpolator1;
254         private Interpolator mInterpolator2;
255         private Interpolator mCrossfader;
256 
InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader)257         InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
258                 Interpolator crossfader) {
259             mInterpolator1 = interpolator1;
260             mInterpolator2 = interpolator2;
261             mCrossfader = crossfader;
262         }
263 
264         @Override
getInterpolation(float input)265         public float getInterpolation(float input) {
266             float t = mCrossfader.getInterpolation(input);
267             return (1 - t) * mInterpolator1.getInterpolation(input)
268                     + t * mInterpolator2.getInterpolation(input);
269         }
270     }
271 
272     /**
273      * An interpolator which interpolates with a fixed velocity.
274      */
275     private static final class VelocityInterpolator implements Interpolator {
276 
277         private float mDurationSeconds;
278         private float mVelocity;
279         private float mDiff;
280 
VelocityInterpolator(float durationSeconds, float velocity, float diff)281         private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
282             mDurationSeconds = durationSeconds;
283             mVelocity = velocity;
284             mDiff = diff;
285         }
286 
287         @Override
getInterpolation(float input)288         public float getInterpolation(float input) {
289             float time = input * mDurationSeconds;
290             return time * mVelocity / mDiff;
291         }
292     }
293 
294     private static class AnimatorProperties {
295         Interpolator interpolator;
296         long duration;
297     }
298 
299 }
300