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