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.util.Log; 22 import android.view.ViewPropertyAnimator; 23 import android.view.animation.Interpolator; 24 import android.view.animation.PathInterpolator; 25 26 import com.android.systemui.Interpolators; 27 import com.android.systemui.statusbar.notification.NotificationUtils; 28 import com.android.systemui.statusbar.phone.StatusBar; 29 30 /** 31 * Utility class to calculate general fling animation when the finger is released. 32 */ 33 public class FlingAnimationUtils { 34 35 private static final String TAG = "FlingAnimationUtils"; 36 37 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f; 38 private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f; 39 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; 42 private static final float MIN_VELOCITY_DP_PER_SECOND = 250; 43 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; 44 45 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f; 46 private final float mSpeedUpFactor; 47 private final float mY2; 48 49 private float mMinVelocityPxPerSecond; 50 private float mMaxLengthSeconds; 51 private float mHighVelocityPxPerSecond; 52 private float mLinearOutSlowInX2; 53 54 private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); 55 private PathInterpolator mInterpolator; 56 private float mCachedStartGradient = -1; 57 private float mCachedVelocityFactor = -1; 58 FlingAnimationUtils(Context ctx, float maxLengthSeconds)59 public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { 60 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 */ FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor)69 public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) { 70 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 */ FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2, float y2)82 public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2, 83 float y2) { 84 mMaxLengthSeconds = maxLengthSeconds; 85 mSpeedUpFactor = speedUpFactor; 86 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; 94 95 mMinVelocityPxPerSecond 96 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 97 mHighVelocityPxPerSecond 98 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 99 } 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 */ apply(Animator animator, float currValue, float endValue, float velocity)110 public void apply(Animator animator, float currValue, float endValue, float velocity) { 111 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 122 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity)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 136 * @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 */ apply(Animator animator, float currValue, float endValue, float velocity, float maxDistance)139 public void apply(Animator animator, float currValue, float endValue, float velocity, 140 float maxDistance) { 141 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 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)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 getProperties(float currValue, float endValue, float velocity, float maxDistance)166 private AnimatorProperties getProperties(float currValue, 167 float endValue, float velocity, float maxDistance) { 168 float maxLengthSeconds = (float) (mMaxLengthSeconds 169 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); 170 float diff = Math.abs(endValue - currValue); 171 float velAbs = Math.abs(velocity); 172 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, 175 mY2 / mLinearOutSlowInX2, velocityFactor); 176 float durationSeconds = startGradient * diff / velAbs; 177 Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor); 178 if (durationSeconds <= maxLengthSeconds) { 179 mAnimatorProperties.interpolator = slowInInterpolator; 180 } else if (velAbs >= mMinVelocityPxPerSecond) { 181 182 // Cross fade between fast-out-slow-in and linear interpolator with current velocity. 183 durationSeconds = maxLengthSeconds; 184 VelocityInterpolator velocityInterpolator 185 = new VelocityInterpolator(durationSeconds, velAbs, diff); 186 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 187 velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN); 188 mAnimatorProperties.interpolator = superInterpolator; 189 } else { 190 191 // Just use a normal interpolator which doesn't take the velocity into account. 192 durationSeconds = maxLengthSeconds; 193 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN; 194 } 195 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 196 return mAnimatorProperties; 197 } 198 getInterpolator(float startGradient, float velocityFactor)199 private Interpolator getInterpolator(float startGradient, float velocityFactor) { 200 if (Float.isNaN(velocityFactor)) { 201 Log.e(TAG, "Invalid velocity factor", new Throwable()); 202 return Interpolators.LINEAR_OUT_SLOW_IN; 203 } 204 if (startGradient != mCachedStartGradient 205 || velocityFactor != mCachedVelocityFactor) { 206 float speedup = mSpeedUpFactor * (1.0f - velocityFactor); 207 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 } 217 mCachedStartGradient = startGradient; 218 mCachedVelocityFactor = velocityFactor; 219 } 220 return mInterpolator; 221 } 222 223 /** 224 * 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 */ applyDismissing(Animator animator, float currValue, float endValue, float velocity, float maxDistance)235 public void applyDismissing(Animator animator, float currValue, float endValue, 236 float velocity, float maxDistance) { 237 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 */ applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)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 getDismissingProperties(float currValue, float endValue, float velocity, float maxDistance)263 private AnimatorProperties getDismissingProperties(float currValue, float endValue, 264 float velocity, float maxDistance) { 265 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); 269 float y2 = calculateLinearOutFasterInY2(velAbs); 270 271 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; 272 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2); 273 float durationSeconds = startGradient * diff / velAbs; 274 if (durationSeconds <= maxLengthSeconds) { 275 mAnimatorProperties.interpolator = mLinearOutFasterIn; 276 } 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( 284 velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN); 285 mAnimatorProperties.interpolator = superInterpolator; 286 } else { 287 288 // Just use a normal interpolator which doesn't take the velocity into account. 289 durationSeconds = maxLengthSeconds; 290 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN; 291 } 292 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 293 return mAnimatorProperties; 294 } 295 296 /** 297 * 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 */ calculateLinearOutFasterInY2(float velocity)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 /** 311 * @return the minimum velocity a gesture needs to have to be considered a fling 312 */ getMinVelocityPxPerSecond()313 public float getMinVelocityPxPerSecond() { 314 return mMinVelocityPxPerSecond; 315 } 316 317 /** 318 * 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 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader)326 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, 327 Interpolator crossfader) { 328 mInterpolator1 = interpolator1; 329 mInterpolator2 = interpolator2; 330 mCrossfader = crossfader; 331 } 332 333 @Override getInterpolation(float input)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 VelocityInterpolator(float durationSeconds, float velocity, float diff)350 private VelocityInterpolator(float durationSeconds, float velocity, float diff) { 351 mDurationSeconds = durationSeconds; 352 mVelocity = velocity; 353 mDiff = diff; 354 } 355 356 @Override getInterpolation(float input)357 public float getInterpolation(float input) { 358 float time = input * mDurationSeconds; 359 return time * mVelocity / mDiff; 360 } 361 } 362 363 private static class AnimatorProperties { 364 Interpolator interpolator; 365 long duration; 366 } 367 368 } 369