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.wm.shell.animation; 18 19 import android.animation.Animator; 20 import android.util.DisplayMetrics; 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 javax.inject.Inject; 27 28 /** 29 * Utility class to calculate general fling animation when the finger is released. 30 */ 31 public class FlingAnimationUtils { 32 33 private static final String TAG = "FlingAnimationUtils"; 34 35 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f; 36 private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f; 37 private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f; 38 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f; 39 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f; 40 private static final float MIN_VELOCITY_DP_PER_SECOND = 250; 41 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; 42 43 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f; 44 private final float mSpeedUpFactor; 45 private final float mY2; 46 47 private float mMinVelocityPxPerSecond; 48 private float mMaxLengthSeconds; 49 private float mHighVelocityPxPerSecond; 50 private float mLinearOutSlowInX2; 51 52 private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); 53 private PathInterpolator mInterpolator; 54 private float mCachedStartGradient = -1; 55 private float mCachedVelocityFactor = -1; 56 FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds)57 public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds) { 58 this(displayMetrics, maxLengthSeconds, 0.0f); 59 } 60 61 /** 62 * @param maxLengthSeconds the longest duration an animation can become in seconds 63 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards 64 * the end of the animation. 0 means it's at the beginning and no 65 * acceleration will take place. 66 */ FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds, float speedUpFactor)67 public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds, 68 float speedUpFactor) { 69 this(displayMetrics, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f); 70 } 71 72 /** 73 * @param maxLengthSeconds the longest duration an animation can become in seconds 74 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards 75 * the end of the animation. 0 means it's at the beginning and no 76 * acceleration will take place. 77 * @param x2 the x value to take for the second point of the bezier spline. If a 78 * value below 0 is provided, the value is automatically calculated. 79 * @param y2 the y value to take for the second point of the bezier spline 80 */ FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds, float speedUpFactor, float x2, float y2)81 public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds, 82 float speedUpFactor, float x2, float y2) { 83 mMaxLengthSeconds = maxLengthSeconds; 84 mSpeedUpFactor = speedUpFactor; 85 if (x2 < 0) { 86 mLinearOutSlowInX2 = interpolate(LINEAR_OUT_SLOW_IN_X2, 87 LINEAR_OUT_SLOW_IN_X2_MAX, 88 mSpeedUpFactor); 89 } else { 90 mLinearOutSlowInX2 = x2; 91 } 92 mY2 = y2; 93 94 mMinVelocityPxPerSecond = MIN_VELOCITY_DP_PER_SECOND * displayMetrics.density; 95 mHighVelocityPxPerSecond = HIGH_VELOCITY_DP_PER_SECOND * displayMetrics.density; 96 } 97 98 /** 99 * Applies the interpolator and length to the animator, such that the fling animation is 100 * consistent with the finger motion. 101 * 102 * @param animator the animator to apply 103 * @param currValue the current value 104 * @param endValue the end value of the animator 105 * @param velocity the current velocity of the motion 106 */ apply(Animator animator, float currValue, float endValue, float velocity)107 public void apply(Animator animator, float currValue, float endValue, float velocity) { 108 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); 109 } 110 111 /** 112 * Applies the interpolator and length to the animator, such that the fling animation is 113 * consistent with the finger motion. 114 * 115 * @param animator the animator to apply 116 * @param currValue the current value 117 * @param endValue the end value of the animator 118 * @param velocity the current velocity of the motion 119 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity)120 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 121 float velocity) { 122 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); 123 } 124 125 /** 126 * Applies the interpolator and length to the animator, such that the fling animation is 127 * consistent with the finger motion. 128 * 129 * @param animator the animator to apply 130 * @param currValue the current value 131 * @param endValue the end value of the animator 132 * @param velocity the current velocity of the motion 133 * @param maxDistance the maximum distance for this interaction; the maximum animation length 134 * gets multiplied by the ratio between the actual distance and this value 135 */ apply(Animator animator, float currValue, float endValue, float velocity, float maxDistance)136 public void apply(Animator animator, float currValue, float endValue, float velocity, 137 float maxDistance) { 138 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 139 maxDistance); 140 animator.setDuration(properties.mDuration); 141 animator.setInterpolator(properties.mInterpolator); 142 } 143 144 /** 145 * Applies the interpolator and length to the animator, such that the fling animation is 146 * consistent with the finger motion. 147 * 148 * @param animator the animator to apply 149 * @param currValue the current value 150 * @param endValue the end value of the animator 151 * @param velocity the current velocity of the motion 152 * @param maxDistance the maximum distance for this interaction; the maximum animation length 153 * gets multiplied by the ratio between the actual distance and this value 154 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)155 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 156 float velocity, float maxDistance) { 157 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 158 maxDistance); 159 animator.setDuration(properties.mDuration); 160 animator.setInterpolator(properties.mInterpolator); 161 } 162 getProperties(float currValue, float endValue, float velocity, float maxDistance)163 private AnimatorProperties getProperties(float currValue, 164 float endValue, float velocity, float maxDistance) { 165 float maxLengthSeconds = (float) (mMaxLengthSeconds 166 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); 167 float diff = Math.abs(endValue - currValue); 168 float velAbs = Math.abs(velocity); 169 float velocityFactor = mSpeedUpFactor == 0.0f 170 ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f); 171 float startGradient = interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT, 172 mY2 / mLinearOutSlowInX2, velocityFactor); 173 float durationSeconds = startGradient * diff / velAbs; 174 Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor); 175 if (durationSeconds <= maxLengthSeconds) { 176 mAnimatorProperties.mInterpolator = slowInInterpolator; 177 } else if (velAbs >= mMinVelocityPxPerSecond) { 178 179 // Cross fade between fast-out-slow-in and linear interpolator with current velocity. 180 durationSeconds = maxLengthSeconds; 181 VelocityInterpolator velocityInterpolator = new VelocityInterpolator( 182 durationSeconds, velAbs, diff); 183 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 184 velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN); 185 mAnimatorProperties.mInterpolator = superInterpolator; 186 } else { 187 188 // Just use a normal interpolator which doesn't take the velocity into account. 189 durationSeconds = maxLengthSeconds; 190 mAnimatorProperties.mInterpolator = Interpolators.FAST_OUT_SLOW_IN; 191 } 192 mAnimatorProperties.mDuration = (long) (durationSeconds * 1000); 193 return mAnimatorProperties; 194 } 195 getInterpolator(float startGradient, float velocityFactor)196 private Interpolator getInterpolator(float startGradient, float velocityFactor) { 197 if (Float.isNaN(velocityFactor)) { 198 Log.e(TAG, "Invalid velocity factor", new Throwable()); 199 return Interpolators.LINEAR_OUT_SLOW_IN; 200 } 201 if (startGradient != mCachedStartGradient 202 || velocityFactor != mCachedVelocityFactor) { 203 float speedup = mSpeedUpFactor * (1.0f - velocityFactor); 204 float x1 = speedup; 205 float y1 = speedup * startGradient; 206 float x2 = mLinearOutSlowInX2; 207 float y2 = mY2; 208 try { 209 mInterpolator = new PathInterpolator(x1, y1, x2, y2); 210 } catch (IllegalArgumentException e) { 211 throw new IllegalArgumentException("Illegal path with " 212 + "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e); 213 } 214 mCachedStartGradient = startGradient; 215 mCachedVelocityFactor = velocityFactor; 216 } 217 return mInterpolator; 218 } 219 220 /** 221 * Applies the interpolator and length to the animator, such that the fling animation is 222 * consistent with the finger motion for the case when the animation is making something 223 * disappear. 224 * 225 * @param animator the animator to apply 226 * @param currValue the current value 227 * @param endValue the end value of the animator 228 * @param velocity the current velocity of the motion 229 * @param maxDistance the maximum distance for this interaction; the maximum animation length 230 * gets multiplied by the ratio between the actual distance and this value 231 */ applyDismissing(Animator animator, float currValue, float endValue, float velocity, float maxDistance)232 public void applyDismissing(Animator animator, float currValue, float endValue, 233 float velocity, float maxDistance) { 234 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 235 maxDistance); 236 animator.setDuration(properties.mDuration); 237 animator.setInterpolator(properties.mInterpolator); 238 } 239 240 /** 241 * Applies the interpolator and length to the animator, such that the fling animation is 242 * consistent with the finger motion for the case when the animation is making something 243 * disappear. 244 * 245 * @param animator the animator to apply 246 * @param currValue the current value 247 * @param endValue the end value of the animator 248 * @param velocity the current velocity of the motion 249 * @param maxDistance the maximum distance for this interaction; the maximum animation length 250 * gets multiplied by the ratio between the actual distance and this value 251 */ applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)252 public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, 253 float velocity, float maxDistance) { 254 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 255 maxDistance); 256 animator.setDuration(properties.mDuration); 257 animator.setInterpolator(properties.mInterpolator); 258 } 259 getDismissingProperties(float currValue, float endValue, float velocity, float maxDistance)260 private AnimatorProperties getDismissingProperties(float currValue, float endValue, 261 float velocity, float maxDistance) { 262 float maxLengthSeconds = (float) (mMaxLengthSeconds 263 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); 264 float diff = Math.abs(endValue - currValue); 265 float velAbs = Math.abs(velocity); 266 float y2 = calculateLinearOutFasterInY2(velAbs); 267 268 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; 269 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2); 270 float durationSeconds = startGradient * diff / velAbs; 271 if (durationSeconds <= maxLengthSeconds) { 272 mAnimatorProperties.mInterpolator = mLinearOutFasterIn; 273 } else if (velAbs >= mMinVelocityPxPerSecond) { 274 275 // Cross fade between linear-out-faster-in and linear interpolator with current 276 // velocity. 277 durationSeconds = maxLengthSeconds; 278 VelocityInterpolator velocityInterpolator = new VelocityInterpolator( 279 durationSeconds, velAbs, diff); 280 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 281 velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN); 282 mAnimatorProperties.mInterpolator = superInterpolator; 283 } else { 284 285 // Just use a normal interpolator which doesn't take the velocity into account. 286 durationSeconds = maxLengthSeconds; 287 mAnimatorProperties.mInterpolator = Interpolators.FAST_OUT_LINEAR_IN; 288 } 289 mAnimatorProperties.mDuration = (long) (durationSeconds * 1000); 290 return mAnimatorProperties; 291 } 292 293 /** 294 * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the 295 * velocity. The faster the velocity, the more "linear" the interpolator gets. 296 * 297 * @param velocity the velocity of the gesture. 298 * @return the y2 control point for a cubic bezier path interpolator 299 */ calculateLinearOutFasterInY2(float velocity)300 private float calculateLinearOutFasterInY2(float velocity) { 301 float t = (velocity - mMinVelocityPxPerSecond) 302 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond); 303 t = Math.max(0, Math.min(1, t)); 304 return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX; 305 } 306 307 /** 308 * @return the minimum velocity a gesture needs to have to be considered a fling 309 */ getMinVelocityPxPerSecond()310 public float getMinVelocityPxPerSecond() { 311 return mMinVelocityPxPerSecond; 312 } 313 314 /** 315 * @return a velocity considered fast 316 */ getHighVelocityPxPerSecond()317 public float getHighVelocityPxPerSecond() { 318 return mHighVelocityPxPerSecond; 319 } 320 321 /** 322 * An interpolator which interpolates two interpolators with an interpolator. 323 */ 324 private static final class InterpolatorInterpolator implements Interpolator { 325 326 private Interpolator mInterpolator1; 327 private Interpolator mInterpolator2; 328 private Interpolator mCrossfader; 329 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader)330 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, 331 Interpolator crossfader) { 332 mInterpolator1 = interpolator1; 333 mInterpolator2 = interpolator2; 334 mCrossfader = crossfader; 335 } 336 337 @Override getInterpolation(float input)338 public float getInterpolation(float input) { 339 float t = mCrossfader.getInterpolation(input); 340 return (1 - t) * mInterpolator1.getInterpolation(input) 341 + t * mInterpolator2.getInterpolation(input); 342 } 343 } 344 345 /** 346 * An interpolator which interpolates with a fixed velocity. 347 */ 348 private static final class VelocityInterpolator implements Interpolator { 349 350 private float mDurationSeconds; 351 private float mVelocity; 352 private float mDiff; 353 VelocityInterpolator(float durationSeconds, float velocity, float diff)354 private VelocityInterpolator(float durationSeconds, float velocity, float diff) { 355 mDurationSeconds = durationSeconds; 356 mVelocity = velocity; 357 mDiff = diff; 358 } 359 360 @Override getInterpolation(float input)361 public float getInterpolation(float input) { 362 float time = input * mDurationSeconds; 363 return time * mVelocity / mDiff; 364 } 365 } 366 367 private static class AnimatorProperties { 368 Interpolator mInterpolator; 369 long mDuration; 370 } 371 372 /** Builder for {@link #FlingAnimationUtils}. */ 373 public static class Builder { 374 private final DisplayMetrics mDisplayMetrics; 375 float mMaxLengthSeconds; 376 float mSpeedUpFactor; 377 float mX2; 378 float mY2; 379 380 @Inject Builder(DisplayMetrics displayMetrics)381 public Builder(DisplayMetrics displayMetrics) { 382 mDisplayMetrics = displayMetrics; 383 reset(); 384 } 385 386 /** Sets the longest duration an animation can become in seconds. */ setMaxLengthSeconds(float maxLengthSeconds)387 public Builder setMaxLengthSeconds(float maxLengthSeconds) { 388 mMaxLengthSeconds = maxLengthSeconds; 389 return this; 390 } 391 392 /** 393 * Sets the factor for how much the slow down should be shifted towards the end of the 394 * animation. 395 */ setSpeedUpFactor(float speedUpFactor)396 public Builder setSpeedUpFactor(float speedUpFactor) { 397 mSpeedUpFactor = speedUpFactor; 398 return this; 399 } 400 401 /** Sets the x value to take for the second point of the bezier spline. */ setX2(float x2)402 public Builder setX2(float x2) { 403 mX2 = x2; 404 return this; 405 } 406 407 /** Sets the y value to take for the second point of the bezier spline. */ setY2(float y2)408 public Builder setY2(float y2) { 409 mY2 = y2; 410 return this; 411 } 412 413 /** Resets all parameters of the builder. */ reset()414 public Builder reset() { 415 mMaxLengthSeconds = 0; 416 mSpeedUpFactor = 0.0f; 417 mX2 = -1.0f; 418 mY2 = 1.0f; 419 420 return this; 421 } 422 423 /** Builds {@link #FlingAnimationUtils}. */ build()424 public FlingAnimationUtils build() { 425 return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor, 426 mX2, mY2); 427 } 428 } 429 interpolate(float start, float end, float amount)430 private static float interpolate(float start, float end, float amount) { 431 return start * (1.0f - amount) + end * amount; 432 } 433 } 434