blob: 525b5b795a03c29b5d08842acacb87c3763bfa73 [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;
Dave Mankoff1373fdb2019-12-18 14:04:37 -050020import android.util.DisplayMetrics;
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;
Dave Mankoff1373fdb2019-12-18 14:04:37 -050028
29import javax.inject.Inject;
Winsonc0d70582016-01-29 10:24:39 -080030
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020031/**
32 * Utility class to calculate general fling animation when the finger is released.
33 */
34public class FlingAnimationUtils {
35
Jorim Jaggi84988eb2019-01-10 17:05:12 +010036 private static final String TAG = "FlingAnimationUtils";
37
Jorim Jaggi1d480692014-05-20 19:41:58 +020038 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
Selim Cinek593ee202016-12-28 15:01:16 +010039 private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f;
Jorim Jaggi2580a9762014-06-25 03:08:25 +020040 private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
41 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
42 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020043 private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020044 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020045
Selim Cinek593ee202016-12-28 15:01:16 +010046 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f;
47 private final float mSpeedUpFactor;
Selim Cinek2411f762016-12-28 17:05:08 +010048 private final float mY2;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020049
Jorim Jaggi1d480692014-05-20 19:41:58 +020050 private float mMinVelocityPxPerSecond;
51 private float mMaxLengthSeconds;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020052 private float mHighVelocityPxPerSecond;
Selim Cinek593ee202016-12-28 15:01:16 +010053 private float mLinearOutSlowInX2;
Jorim Jaggi1d480692014-05-20 19:41:58 +020054
Selim Cinek4c6969a2014-05-26 19:22:17 +020055 private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
Selim Cinek593ee202016-12-28 15:01:16 +010056 private PathInterpolator mInterpolator;
57 private float mCachedStartGradient = -1;
58 private float mCachedVelocityFactor = -1;
Selim Cinek4c6969a2014-05-26 19:22:17 +020059
Dave Mankoff1373fdb2019-12-18 14:04:37 -050060 public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds) {
61 this(displayMetrics, maxLengthSeconds, 0.0f);
Selim Cinek593ee202016-12-28 15:01:16 +010062 }
63
64 /**
65 * @param maxLengthSeconds the longest duration an animation can become in seconds
66 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
67 * the end of the animation. 0 means it's at the beginning and no
68 * acceleration will take place.
69 */
Dave Mankoff1373fdb2019-12-18 14:04:37 -050070 public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds,
71 float speedUpFactor) {
72 this(displayMetrics, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
Selim Cinek2411f762016-12-28 17:05:08 +010073 }
74
75 /**
76 * @param maxLengthSeconds the longest duration an animation can become in seconds
77 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
78 * the end of the animation. 0 means it's at the beginning and no
79 * acceleration will take place.
80 * @param x2 the x value to take for the second point of the bezier spline. If a value below 0
81 * is provided, the value is automatically calculated.
82 * @param y2 the y value to take for the second point of the bezier spline
83 */
Dave Mankoff1373fdb2019-12-18 14:04:37 -050084 public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds,
85 float speedUpFactor, float x2, float y2) {
Jorim Jaggi1d480692014-05-20 19:41:58 +020086 mMaxLengthSeconds = maxLengthSeconds;
Selim Cinek593ee202016-12-28 15:01:16 +010087 mSpeedUpFactor = speedUpFactor;
Selim Cinek2411f762016-12-28 17:05:08 +010088 if (x2 < 0) {
89 mLinearOutSlowInX2 = NotificationUtils.interpolate(LINEAR_OUT_SLOW_IN_X2,
90 LINEAR_OUT_SLOW_IN_X2_MAX,
91 mSpeedUpFactor);
92 } else {
93 mLinearOutSlowInX2 = x2;
94 }
95 mY2 = y2;
Selim Cinek593ee202016-12-28 15:01:16 +010096
Dave Mankoff1373fdb2019-12-18 14:04:37 -050097 mMinVelocityPxPerSecond = MIN_VELOCITY_DP_PER_SECOND * displayMetrics.density;
98 mHighVelocityPxPerSecond = HIGH_VELOCITY_DP_PER_SECOND * displayMetrics.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
Dave Mankoff1373fdb2019-12-18 14:04:37 -0500368 public static class Builder {
369 private final DisplayMetrics mDisplayMetrics;
370 float mMaxLengthSeconds;
371 float mSpeedUpFactor = 0.0f;
372 float mX2 = -1.0f;
373 float mY2 = 1.0f;
374
375 @Inject
376 public Builder(DisplayMetrics displayMetrics) {
377 mDisplayMetrics = displayMetrics;
378 }
379
380 public Builder setMaxLengthSeconds(float maxLengthSeconds) {
381 mMaxLengthSeconds = maxLengthSeconds;
382 return this;
383 }
384
385 public Builder setSpeedUpFactor(float speedUpFactor) {
386 mSpeedUpFactor = speedUpFactor;
387 return this;
388 }
389
390 public Builder setX2(float x2) {
391 mX2 = x2;
392 return this;
393 }
394
395 public Builder setY2(float y2) {
396 mY2 = y2;
397 return this;
398 }
399
400 public FlingAnimationUtils build() {
401 return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor,
402 mX2, mY2);
403 }
404 }
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200405}