blob: 0fa088b703a4638b18a05c94ed07cf6af2f53790 [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;
Selim Cinek4c6969a2014-05-26 19:22:17 +020021import android.view.ViewPropertyAnimator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020022import android.view.animation.AnimationUtils;
23import android.view.animation.Interpolator;
24import android.view.animation.PathInterpolator;
25
26/**
27 * Utility class to calculate general fling animation when the finger is released.
28 */
29public class FlingAnimationUtils {
30
Jorim Jaggi1d480692014-05-20 19:41:58 +020031 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
Jorim Jaggi2580a9762014-06-25 03:08:25 +020032 private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
33 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
34 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020035 private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020036 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020037
38 /**
39 * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve
40 */
Jorim Jaggi1d480692014-05-20 19:41:58 +020041 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020042
43 private Interpolator mLinearOutSlowIn;
44 private Interpolator mFastOutSlowIn;
Jorim Jaggi1d480692014-05-20 19:41:58 +020045 private Interpolator mFastOutLinearIn;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020046
Jorim Jaggi1d480692014-05-20 19:41:58 +020047 private float mMinVelocityPxPerSecond;
48 private float mMaxLengthSeconds;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020049 private float mHighVelocityPxPerSecond;
Jorim Jaggi1d480692014-05-20 19:41:58 +020050
Selim Cinek4c6969a2014-05-26 19:22:17 +020051 private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
52
Jorim Jaggi1d480692014-05-20 19:41:58 +020053 public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
54 mMaxLengthSeconds = maxLengthSeconds;
55 mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020056 mFastOutSlowIn
57 = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
Jorim Jaggi1d480692014-05-20 19:41:58 +020058 mFastOutLinearIn
59 = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_linear_in);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020060 mMinVelocityPxPerSecond
61 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
Jorim Jaggiefbd7e32014-05-21 17:14:18 +020062 mHighVelocityPxPerSecond
63 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020064 }
65
66 /**
67 * Applies the interpolator and length to the animator, such that the fling animation is
68 * consistent with the finger motion.
69 *
70 * @param animator the animator to apply
71 * @param currValue the current value
72 * @param endValue the end value of the animator
73 * @param velocity the current velocity of the motion
74 */
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +020075 public void apply(Animator animator, float currValue, float endValue, float velocity) {
Jorim Jaggi1d480692014-05-20 19:41:58 +020076 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
77 }
78
79 /**
80 * Applies the interpolator and length to the animator, such that the fling animation is
81 * consistent with the finger motion.
82 *
83 * @param animator the animator to apply
84 * @param currValue the current value
85 * @param endValue the end value of the animator
86 * @param velocity the current velocity of the motion
Selim Cinek4c6969a2014-05-26 19:22:17 +020087 */
88 public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
89 float velocity) {
90 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
91 }
92
93 /**
94 * Applies the interpolator and length to the animator, such that the fling animation is
95 * consistent with the finger motion.
96 *
97 * @param animator the animator to apply
98 * @param currValue the current value
99 * @param endValue the end value of the animator
100 * @param velocity the current velocity of the motion
Jorim Jaggi1d480692014-05-20 19:41:58 +0200101 * @param maxDistance the maximum distance for this interaction; the maximum animation length
102 * gets multiplied by the ratio between the actual distance and this value
103 */
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200104 public void apply(Animator animator, float currValue, float endValue, float velocity,
Jorim Jaggi1d480692014-05-20 19:41:58 +0200105 float maxDistance) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200106 AnimatorProperties properties = getProperties(currValue, endValue, velocity,
107 maxDistance);
108 animator.setDuration(properties.duration);
109 animator.setInterpolator(properties.interpolator);
110 }
111
112 /**
113 * Applies the interpolator and length to the animator, such that the fling animation is
114 * consistent with the finger motion.
115 *
116 * @param animator the animator to apply
117 * @param currValue the current value
118 * @param endValue the end value of the animator
119 * @param velocity the current velocity of the motion
120 * @param maxDistance the maximum distance for this interaction; the maximum animation length
121 * gets multiplied by the ratio between the actual distance and this value
122 */
123 public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
124 float velocity, float maxDistance) {
125 AnimatorProperties properties = getProperties(currValue, endValue, velocity,
126 maxDistance);
127 animator.setDuration(properties.duration);
128 animator.setInterpolator(properties.interpolator);
129 }
130
131 private AnimatorProperties getProperties(float currValue,
132 float endValue, float velocity, float maxDistance) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200133 float maxLengthSeconds = (float) (mMaxLengthSeconds
134 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200135 float diff = Math.abs(endValue - currValue);
136 float velAbs = Math.abs(velocity);
137 float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200138 if (durationSeconds <= maxLengthSeconds) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200139 mAnimatorProperties.interpolator = mLinearOutSlowIn;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200140 } else if (velAbs >= mMinVelocityPxPerSecond) {
141
142 // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
Jorim Jaggi1d480692014-05-20 19:41:58 +0200143 durationSeconds = maxLengthSeconds;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200144 VelocityInterpolator velocityInterpolator
145 = new VelocityInterpolator(durationSeconds, velAbs, diff);
146 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
147 velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200148 mAnimatorProperties.interpolator = superInterpolator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200149 } else {
150
151 // Just use a normal interpolator which doesn't take the velocity into account.
Jorim Jaggi1d480692014-05-20 19:41:58 +0200152 durationSeconds = maxLengthSeconds;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200153 mAnimatorProperties.interpolator = mFastOutSlowIn;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200154 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200155 mAnimatorProperties.duration = (long) (durationSeconds * 1000);
156 return mAnimatorProperties;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200157 }
158
159 /**
Jorim Jaggi1d480692014-05-20 19:41:58 +0200160 * Applies the interpolator and length to the animator, such that the fling animation is
161 * consistent with the finger motion for the case when the animation is making something
162 * disappear.
163 *
164 * @param animator the animator to apply
165 * @param currValue the current value
166 * @param endValue the end value of the animator
167 * @param velocity the current velocity of the motion
168 * @param maxDistance the maximum distance for this interaction; the maximum animation length
169 * gets multiplied by the ratio between the actual distance and this value
170 */
Jorim Jaggib6cdcbc2014-07-25 21:58:29 +0200171 public void applyDismissing(Animator animator, float currValue, float endValue,
Jorim Jaggi1d480692014-05-20 19:41:58 +0200172 float velocity, float maxDistance) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200173 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
174 maxDistance);
175 animator.setDuration(properties.duration);
176 animator.setInterpolator(properties.interpolator);
177 }
178
179 /**
180 * Applies the interpolator and length to the animator, such that the fling animation is
181 * consistent with the finger motion for the case when the animation is making something
182 * disappear.
183 *
184 * @param animator the animator to apply
185 * @param currValue the current value
186 * @param endValue the end value of the animator
187 * @param velocity the current velocity of the motion
188 * @param maxDistance the maximum distance for this interaction; the maximum animation length
189 * gets multiplied by the ratio between the actual distance and this value
190 */
191 public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
192 float velocity, float maxDistance) {
193 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
194 maxDistance);
195 animator.setDuration(properties.duration);
196 animator.setInterpolator(properties.interpolator);
197 }
198
199 private AnimatorProperties getDismissingProperties(float currValue, float endValue,
200 float velocity, float maxDistance) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200201 float maxLengthSeconds = (float) (mMaxLengthSeconds
202 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
203 float diff = Math.abs(endValue - currValue);
204 float velAbs = Math.abs(velocity);
Jorim Jaggiefbd7e32014-05-21 17:14:18 +0200205 float y2 = calculateLinearOutFasterInY2(velAbs);
206
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200207 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
208 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
Jorim Jaggiefbd7e32014-05-21 17:14:18 +0200209 float durationSeconds = startGradient * diff / velAbs;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200210 if (durationSeconds <= maxLengthSeconds) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200211 mAnimatorProperties.interpolator = mLinearOutFasterIn;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200212 } else if (velAbs >= mMinVelocityPxPerSecond) {
213
214 // Cross fade between linear-out-faster-in and linear interpolator with current
215 // velocity.
216 durationSeconds = maxLengthSeconds;
217 VelocityInterpolator velocityInterpolator
218 = new VelocityInterpolator(durationSeconds, velAbs, diff);
219 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
220 velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200221 mAnimatorProperties.interpolator = superInterpolator;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200222 } else {
223
224 // Just use a normal interpolator which doesn't take the velocity into account.
225 durationSeconds = maxLengthSeconds;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200226 mAnimatorProperties.interpolator = mFastOutLinearIn;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200227 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200228 mAnimatorProperties.duration = (long) (durationSeconds * 1000);
229 return mAnimatorProperties;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200230 }
231
232 /**
Jorim Jaggiefbd7e32014-05-21 17:14:18 +0200233 * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
234 * velocity. The faster the velocity, the more "linear" the interpolator gets.
235 *
236 * @param velocity the velocity of the gesture.
237 * @return the y2 control point for a cubic bezier path interpolator
238 */
239 private float calculateLinearOutFasterInY2(float velocity) {
240 float t = (velocity - mMinVelocityPxPerSecond)
241 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
242 t = Math.max(0, Math.min(1, t));
243 return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
244 }
245
246 /**
Jorim Jaggi1d480692014-05-20 19:41:58 +0200247 * @return the minimum velocity a gesture needs to have to be considered a fling
248 */
249 public float getMinVelocityPxPerSecond() {
250 return mMinVelocityPxPerSecond;
251 }
252
253 /**
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200254 * An interpolator which interpolates two interpolators with an interpolator.
255 */
256 private static final class InterpolatorInterpolator implements Interpolator {
257
258 private Interpolator mInterpolator1;
259 private Interpolator mInterpolator2;
260 private Interpolator mCrossfader;
261
262 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
263 Interpolator crossfader) {
264 mInterpolator1 = interpolator1;
265 mInterpolator2 = interpolator2;
266 mCrossfader = crossfader;
267 }
268
269 @Override
270 public float getInterpolation(float input) {
271 float t = mCrossfader.getInterpolation(input);
272 return (1 - t) * mInterpolator1.getInterpolation(input)
273 + t * mInterpolator2.getInterpolation(input);
274 }
275 }
276
277 /**
278 * An interpolator which interpolates with a fixed velocity.
279 */
280 private static final class VelocityInterpolator implements Interpolator {
281
282 private float mDurationSeconds;
283 private float mVelocity;
284 private float mDiff;
285
286 private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
287 mDurationSeconds = durationSeconds;
288 mVelocity = velocity;
289 mDiff = diff;
290 }
291
292 @Override
293 public float getInterpolation(float input) {
294 float time = input * mDurationSeconds;
295 return time * mVelocity / mDiff;
296 }
297 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200298
299 private static class AnimatorProperties {
300 Interpolator interpolator;
301 long duration;
302 }
303
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200304}