blob: d4272605924f0e65f88a110f7019bc3b125414b4 [file] [log] [blame]
Jorim Jaggi87cd5e72014-05-12 23:29:10 +02001/*
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
17package com.android.systemui.statusbar;
18
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +020019import android.animation.Animator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020020import android.content.Context;
Jorim Jaggi8e2189d2019-01-08 12:43:07 +010021import android.util.Log;
Selim Cinek4c6969a2014-05-26 19:22:17 +020022import android.view.ViewPropertyAnimator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020023import android.view.animation.Interpolator;
24import android.view.animation.PathInterpolator;
25
Winsonc0d70582016-01-29 10:24:39 -080026import com.android.systemui.Interpolators;
Selim Cinek593ee202016-12-28 15:01:16 +010027import com.android.systemui.statusbar.notification.NotificationUtils;
Jorim Jaggi8e2189d2019-01-08 12:43:07 +010028import com.android.systemui.statusbar.phone.StatusBar;
Winsonc0d70582016-01-29 10:24:39 -080029
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020030/**
31 * Utility class to calculate general fling animation when the finger is released.
32 */
33public class FlingAnimationUtils {
34
Jorim Jaggi84988eb2019-01-10 17:05:12 +010035 private static final String TAG = "FlingAnimationUtils";
36
Jorim Jaggi1d480692014-05-20 19:41:58 +020037 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
Selim Cinek593ee202016-12-28 15:01:16 +010038 private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f;
Jorim Jaggi2580a9762014-06-25 03:08:25 +020039 private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
40 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
41 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020042 private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020043 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020044
Selim Cinek593ee202016-12-28 15:01:16 +010045 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f;
46 private final float mSpeedUpFactor;
Selim Cinek2411f762016-12-28 17:05:08 +010047 private final float mY2;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020048
Jorim Jaggi1d480692014-05-20 19:41:58 +020049 private float mMinVelocityPxPerSecond;
50 private float mMaxLengthSeconds;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020051 private float mHighVelocityPxPerSecond;
Selim Cinek593ee202016-12-28 15:01:16 +010052 private float mLinearOutSlowInX2;
Jorim Jaggi1d480692014-05-20 19:41:58 +020053
Selim Cinek4c6969a2014-05-26 19:22:17 +020054 private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
Selim Cinek593ee202016-12-28 15:01:16 +010055 private PathInterpolator mInterpolator;
56 private float mCachedStartGradient = -1;
57 private float mCachedVelocityFactor = -1;
Selim Cinek4c6969a2014-05-26 19:22:17 +020058
Jorim Jaggi1d480692014-05-20 19:41:58 +020059 public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
Selim Cinek593ee202016-12-28 15:01:16 +010060 this(ctx, maxLengthSeconds, 0.0f);
61 }
62
63 /**
64 * @param maxLengthSeconds the longest duration an animation can become in seconds
65 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
66 * the end of the animation. 0 means it's at the beginning and no
67 * acceleration will take place.
68 */
69 public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) {
Selim Cinek2411f762016-12-28 17:05:08 +010070 this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
71 }
72
73 /**
74 * @param maxLengthSeconds the longest duration an animation can become in seconds
75 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
76 * the end of the animation. 0 means it's at the beginning and no
77 * acceleration will take place.
78 * @param x2 the x value to take for the second point of the bezier spline. If a value below 0
79 * is provided, the value is automatically calculated.
80 * @param y2 the y value to take for the second point of the bezier spline
81 */
82 public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2,
83 float y2) {
Jorim Jaggi1d480692014-05-20 19:41:58 +020084 mMaxLengthSeconds = maxLengthSeconds;
Selim Cinek593ee202016-12-28 15:01:16 +010085 mSpeedUpFactor = speedUpFactor;
Selim Cinek2411f762016-12-28 17:05:08 +010086 if (x2 < 0) {
87 mLinearOutSlowInX2 = NotificationUtils.interpolate(LINEAR_OUT_SLOW_IN_X2,
88 LINEAR_OUT_SLOW_IN_X2_MAX,
89 mSpeedUpFactor);
90 } else {
91 mLinearOutSlowInX2 = x2;
92 }
93 mY2 = y2;
Selim Cinek593ee202016-12-28 15:01:16 +010094
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020095 mMinVelocityPxPerSecond
96 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020097 mHighVelocityPxPerSecond
98 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020099 }
100
101 /**
102 * Applies the interpolator and length to the animator, such that the fling animation is
103 * consistent with the finger motion.
104 *
105 * @param animator the animator to apply
106 * @param currValue the current value
107 * @param endValue the end value of the animator
108 * @param velocity the current velocity of the motion
109 */
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200110 public void apply(Animator animator, float currValue, float endValue, float velocity) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200111 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
112 }
113
114 /**
115 * Applies the interpolator and length to the animator, such that the fling animation is
116 * consistent with the finger motion.
117 *
118 * @param animator the animator to apply
119 * @param currValue the current value
120 * @param endValue the end value of the animator
121 * @param velocity the current velocity of the motion
Selim Cinek4c6969a2014-05-26 19:22:17 +0200122 */
123 public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
124 float velocity) {
125 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
126 }
127
128 /**
129 * Applies the interpolator and length to the animator, such that the fling animation is
130 * consistent with the finger motion.
131 *
132 * @param animator the animator to apply
133 * @param currValue the current value
134 * @param endValue the end value of the animator
135 * @param velocity the current velocity of the motion
Jorim Jaggi1d480692014-05-20 19:41:58 +0200136 * @param maxDistance the maximum distance for this interaction; the maximum animation length
137 * gets multiplied by the ratio between the actual distance and this value
138 */
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200139 public void apply(Animator animator, float currValue, float endValue, float velocity,
Jorim Jaggi1d480692014-05-20 19:41:58 +0200140 float maxDistance) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200141 AnimatorProperties properties = getProperties(currValue, endValue, velocity,
142 maxDistance);
143 animator.setDuration(properties.duration);
144 animator.setInterpolator(properties.interpolator);
145 }
146
147 /**
148 * Applies the interpolator and length to the animator, such that the fling animation is
149 * consistent with the finger motion.
150 *
151 * @param animator the animator to apply
152 * @param currValue the current value
153 * @param endValue the end value of the animator
154 * @param velocity the current velocity of the motion
155 * @param maxDistance the maximum distance for this interaction; the maximum animation length
156 * gets multiplied by the ratio between the actual distance and this value
157 */
158 public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
159 float velocity, float maxDistance) {
160 AnimatorProperties properties = getProperties(currValue, endValue, velocity,
161 maxDistance);
162 animator.setDuration(properties.duration);
163 animator.setInterpolator(properties.interpolator);
164 }
165
166 private AnimatorProperties getProperties(float currValue,
167 float endValue, float velocity, float maxDistance) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200168 float maxLengthSeconds = (float) (mMaxLengthSeconds
169 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200170 float diff = Math.abs(endValue - currValue);
171 float velAbs = Math.abs(velocity);
Selim Cinek593ee202016-12-28 15:01:16 +0100172 float velocityFactor = mSpeedUpFactor == 0.0f
173 ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f);
174 float startGradient = NotificationUtils.interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT,
Selim Cinek2411f762016-12-28 17:05:08 +0100175 mY2 / mLinearOutSlowInX2, velocityFactor);
Selim Cinek593ee202016-12-28 15:01:16 +0100176 float durationSeconds = startGradient * diff / velAbs;
177 Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200178 if (durationSeconds <= maxLengthSeconds) {
Selim Cinek593ee202016-12-28 15:01:16 +0100179 mAnimatorProperties.interpolator = slowInInterpolator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200180 } else if (velAbs >= mMinVelocityPxPerSecond) {
181
182 // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
Jorim Jaggi1d480692014-05-20 19:41:58 +0200183 durationSeconds = maxLengthSeconds;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200184 VelocityInterpolator velocityInterpolator
185 = new VelocityInterpolator(durationSeconds, velAbs, diff);
186 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
Selim Cinek593ee202016-12-28 15:01:16 +0100187 velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200188 mAnimatorProperties.interpolator = superInterpolator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200189 } else {
190
191 // Just use a normal interpolator which doesn't take the velocity into account.
Jorim Jaggi1d480692014-05-20 19:41:58 +0200192 durationSeconds = maxLengthSeconds;
Selim Cinekc18010f2016-01-20 13:41:30 -0800193 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200194 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200195 mAnimatorProperties.duration = (long) (durationSeconds * 1000);
196 return mAnimatorProperties;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200197 }
198
Selim Cinek593ee202016-12-28 15:01:16 +0100199 private Interpolator getInterpolator(float startGradient, float velocityFactor) {
Jorim Jaggi84988eb2019-01-10 17:05:12 +0100200 if (Float.isNaN(velocityFactor)) {
201 Log.e(TAG, "Invalid velocity factor", new Throwable());
202 return Interpolators.LINEAR_OUT_SLOW_IN;
203 }
Selim Cinek593ee202016-12-28 15:01:16 +0100204 if (startGradient != mCachedStartGradient
205 || velocityFactor != mCachedVelocityFactor) {
206 float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
Jorim Jaggi8e2189d2019-01-08 12:43:07 +0100207 float x1 = speedup;
208 float y1 = speedup * startGradient;
209 float x2 = mLinearOutSlowInX2;
210 float y2 = mY2;
211 try {
212 mInterpolator = new PathInterpolator(x1, y1, x2, y2);
213 } catch (IllegalArgumentException e) {
214 throw new IllegalArgumentException("Illegal path with "
215 + "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e);
216 }
Selim Cinek593ee202016-12-28 15:01:16 +0100217 mCachedStartGradient = startGradient;
218 mCachedVelocityFactor = velocityFactor;
219 }
220 return mInterpolator;
221 }
222
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200223 /**
Jorim Jaggi1d480692014-05-20 19:41:58 +0200224 * Applies the interpolator and length to the animator, such that the fling animation is
225 * consistent with the finger motion for the case when the animation is making something
226 * disappear.
227 *
228 * @param animator the animator to apply
229 * @param currValue the current value
230 * @param endValue the end value of the animator
231 * @param velocity the current velocity of the motion
232 * @param maxDistance the maximum distance for this interaction; the maximum animation length
233 * gets multiplied by the ratio between the actual distance and this value
234 */
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200235 public void applyDismissing(Animator animator, float currValue, float endValue,
Jorim Jaggi1d480692014-05-20 19:41:58 +0200236 float velocity, float maxDistance) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200237 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
238 maxDistance);
239 animator.setDuration(properties.duration);
240 animator.setInterpolator(properties.interpolator);
241 }
242
243 /**
244 * Applies the interpolator and length to the animator, such that the fling animation is
245 * consistent with the finger motion for the case when the animation is making something
246 * disappear.
247 *
248 * @param animator the animator to apply
249 * @param currValue the current value
250 * @param endValue the end value of the animator
251 * @param velocity the current velocity of the motion
252 * @param maxDistance the maximum distance for this interaction; the maximum animation length
253 * gets multiplied by the ratio between the actual distance and this value
254 */
255 public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
256 float velocity, float maxDistance) {
257 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
258 maxDistance);
259 animator.setDuration(properties.duration);
260 animator.setInterpolator(properties.interpolator);
261 }
262
263 private AnimatorProperties getDismissingProperties(float currValue, float endValue,
264 float velocity, float maxDistance) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200265 float maxLengthSeconds = (float) (mMaxLengthSeconds
266 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
267 float diff = Math.abs(endValue - currValue);
268 float velAbs = Math.abs(velocity);
Jorim Jaggiefbd7e32014-05-21 17:14:18 +0200269 float y2 = calculateLinearOutFasterInY2(velAbs);
270
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200271 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
272 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
Jorim Jaggiefbd7e32014-05-21 17:14:18 +0200273 float durationSeconds = startGradient * diff / velAbs;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200274 if (durationSeconds <= maxLengthSeconds) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200275 mAnimatorProperties.interpolator = mLinearOutFasterIn;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200276 } else if (velAbs >= mMinVelocityPxPerSecond) {
277
278 // Cross fade between linear-out-faster-in and linear interpolator with current
279 // velocity.
280 durationSeconds = maxLengthSeconds;
281 VelocityInterpolator velocityInterpolator
282 = new VelocityInterpolator(durationSeconds, velAbs, diff);
283 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
Selim Cinek593ee202016-12-28 15:01:16 +0100284 velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200285 mAnimatorProperties.interpolator = superInterpolator;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200286 } else {
287
288 // Just use a normal interpolator which doesn't take the velocity into account.
289 durationSeconds = maxLengthSeconds;
Selim Cinekc18010f2016-01-20 13:41:30 -0800290 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200291 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200292 mAnimatorProperties.duration = (long) (durationSeconds * 1000);
293 return mAnimatorProperties;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200294 }
295
296 /**
Jorim Jaggiefbd7e32014-05-21 17:14:18 +0200297 * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
298 * velocity. The faster the velocity, the more "linear" the interpolator gets.
299 *
300 * @param velocity the velocity of the gesture.
301 * @return the y2 control point for a cubic bezier path interpolator
302 */
303 private float calculateLinearOutFasterInY2(float velocity) {
304 float t = (velocity - mMinVelocityPxPerSecond)
305 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
306 t = Math.max(0, Math.min(1, t));
307 return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
308 }
309
310 /**
Jorim Jaggi1d480692014-05-20 19:41:58 +0200311 * @return the minimum velocity a gesture needs to have to be considered a fling
312 */
313 public float getMinVelocityPxPerSecond() {
314 return mMinVelocityPxPerSecond;
315 }
316
317 /**
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200318 * An interpolator which interpolates two interpolators with an interpolator.
319 */
320 private static final class InterpolatorInterpolator implements Interpolator {
321
322 private Interpolator mInterpolator1;
323 private Interpolator mInterpolator2;
324 private Interpolator mCrossfader;
325
326 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
327 Interpolator crossfader) {
328 mInterpolator1 = interpolator1;
329 mInterpolator2 = interpolator2;
330 mCrossfader = crossfader;
331 }
332
333 @Override
334 public float getInterpolation(float input) {
335 float t = mCrossfader.getInterpolation(input);
336 return (1 - t) * mInterpolator1.getInterpolation(input)
337 + t * mInterpolator2.getInterpolation(input);
338 }
339 }
340
341 /**
342 * An interpolator which interpolates with a fixed velocity.
343 */
344 private static final class VelocityInterpolator implements Interpolator {
345
346 private float mDurationSeconds;
347 private float mVelocity;
348 private float mDiff;
349
350 private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
351 mDurationSeconds = durationSeconds;
352 mVelocity = velocity;
353 mDiff = diff;
354 }
355
356 @Override
357 public float getInterpolation(float input) {
358 float time = input * mDurationSeconds;
359 return time * mVelocity / mDiff;
360 }
361 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200362
363 private static class AnimatorProperties {
364 Interpolator interpolator;
365 long duration;
366 }
367
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200368}