1 /* 2 * Copyright (C) 2017 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.launcher3.notification; 18 19 import android.animation.Animator; 20 import android.content.Context; 21 import android.view.ViewPropertyAnimator; 22 import android.view.animation.Interpolator; 23 import android.view.animation.PathInterpolator; 24 25 /** 26 * Utility class to calculate general fling animation when the finger is released. 27 * 28 * This class was copied from com.android.systemui.statusbar. 29 */ 30 public class FlingAnimationUtils { 31 32 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f; 33 private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f; 34 private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f; 35 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f; 36 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f; 37 private static final float MIN_VELOCITY_DP_PER_SECOND = 250; 38 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; 39 40 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f; 41 private final float mSpeedUpFactor; 42 private final float mY2; 43 44 private float mMinVelocityPxPerSecond; 45 private float mMaxLengthSeconds; 46 private float mHighVelocityPxPerSecond; 47 private float mLinearOutSlowInX2; 48 49 private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); 50 private PathInterpolator mInterpolator; 51 private float mCachedStartGradient = -1; 52 private float mCachedVelocityFactor = -1; 53 FlingAnimationUtils(Context ctx, float maxLengthSeconds)54 public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { 55 this(ctx, maxLengthSeconds, 0.0f); 56 } 57 58 /** 59 * @param maxLengthSeconds the longest duration an animation can become in seconds 60 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards 61 * the end of the animation. 0 means it's at the beginning and no 62 * acceleration will take place. 63 */ FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor)64 public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) { 65 this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f); 66 } 67 68 /** 69 * @param maxLengthSeconds the longest duration an animation can become in seconds 70 * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards 71 * the end of the animation. 0 means it's at the beginning and no 72 * acceleration will take place. 73 * @param x2 the x value to take for the second point of the bezier spline. If a value below 0 74 * is provided, the value is automatically calculated. 75 * @param y2 the y value to take for the second point of the bezier spline 76 */ FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2, float y2)77 public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2, 78 float y2) { 79 mMaxLengthSeconds = maxLengthSeconds; 80 mSpeedUpFactor = speedUpFactor; 81 if (x2 < 0) { 82 mLinearOutSlowInX2 = interpolate(LINEAR_OUT_SLOW_IN_X2, 83 LINEAR_OUT_SLOW_IN_X2_MAX, 84 mSpeedUpFactor); 85 } else { 86 mLinearOutSlowInX2 = x2; 87 } 88 mY2 = y2; 89 90 mMinVelocityPxPerSecond 91 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 92 mHighVelocityPxPerSecond 93 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 94 } 95 interpolate(float start, float end, float amount)96 private static float interpolate(float start, float end, float amount) { 97 return start * (1.0f - amount) + end * amount; 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(ViewPropertyAnimator animator, float currValue, float endValue, float velocity)122 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 123 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 * @param maxDistance the maximum distance for this interaction; the maximum animation length 136 * gets multiplied by the ratio between the actual distance and this value 137 */ apply(Animator animator, float currValue, float endValue, float velocity, float maxDistance)138 public void apply(Animator animator, float currValue, float endValue, float velocity, 139 float maxDistance) { 140 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 141 maxDistance); 142 animator.setDuration(properties.duration); 143 animator.setInterpolator(properties.interpolator); 144 } 145 146 /** 147 * Applies the interpolator and length to the animator, such that the fling animation is 148 * consistent with the finger motion. 149 * 150 * @param animator the animator to apply 151 * @param currValue the current value 152 * @param endValue the end value of the animator 153 * @param velocity the current velocity of the motion 154 * @param maxDistance the maximum distance for this interaction; the maximum animation length 155 * gets multiplied by the ratio between the actual distance and this value 156 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)157 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 158 float velocity, float maxDistance) { 159 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 160 maxDistance); 161 animator.setDuration(properties.duration); 162 animator.setInterpolator(properties.interpolator); 163 } 164 getProperties(float currValue, float endValue, float velocity, float maxDistance)165 private AnimatorProperties getProperties(float currValue, 166 float endValue, float velocity, float maxDistance) { 167 float maxLengthSeconds = (float) (mMaxLengthSeconds 168 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); 169 float diff = Math.abs(endValue - currValue); 170 float velAbs = Math.abs(velocity); 171 float velocityFactor = mSpeedUpFactor == 0.0f 172 ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f); 173 float startGradient = interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT, 174 mY2 / mLinearOutSlowInX2, velocityFactor); 175 float durationSeconds = startGradient * diff / velAbs; 176 Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor); 177 if (durationSeconds <= maxLengthSeconds) { 178 mAnimatorProperties.interpolator = slowInInterpolator; 179 } else if (velAbs >= mMinVelocityPxPerSecond) { 180 181 // Cross fade between fast-out-slow-in and linear interpolator with current velocity. 182 durationSeconds = maxLengthSeconds; 183 VelocityInterpolator velocityInterpolator 184 = new VelocityInterpolator(durationSeconds, velAbs, diff); 185 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 186 velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN); 187 mAnimatorProperties.interpolator = superInterpolator; 188 } else { 189 190 // Just use a normal interpolator which doesn't take the velocity into account. 191 durationSeconds = maxLengthSeconds; 192 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN; 193 } 194 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 195 return mAnimatorProperties; 196 } 197 getInterpolator(float startGradient, float velocityFactor)198 private Interpolator getInterpolator(float startGradient, float velocityFactor) { 199 if (startGradient != mCachedStartGradient 200 || velocityFactor != mCachedVelocityFactor) { 201 float speedup = mSpeedUpFactor * (1.0f - velocityFactor); 202 mInterpolator = new PathInterpolator(speedup, 203 speedup * startGradient, 204 mLinearOutSlowInX2, mY2); 205 mCachedStartGradient = startGradient; 206 mCachedVelocityFactor = velocityFactor; 207 } 208 return mInterpolator; 209 } 210 211 /** 212 * Applies the interpolator and length to the animator, such that the fling animation is 213 * consistent with the finger motion for the case when the animation is making something 214 * disappear. 215 * 216 * @param animator the animator to apply 217 * @param currValue the current value 218 * @param endValue the end value of the animator 219 * @param velocity the current velocity of the motion 220 * @param maxDistance the maximum distance for this interaction; the maximum animation length 221 * gets multiplied by the ratio between the actual distance and this value 222 */ applyDismissing(Animator animator, float currValue, float endValue, float velocity, float maxDistance)223 public void applyDismissing(Animator animator, float currValue, float endValue, 224 float velocity, float maxDistance) { 225 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 226 maxDistance); 227 animator.setDuration(properties.duration); 228 animator.setInterpolator(properties.interpolator); 229 } 230 231 /** 232 * Applies the interpolator and length to the animator, such that the fling animation is 233 * consistent with the finger motion for the case when the animation is making something 234 * disappear. 235 * 236 * @param animator the animator to apply 237 * @param currValue the current value 238 * @param endValue the end value of the animator 239 * @param velocity the current velocity of the motion 240 * @param maxDistance the maximum distance for this interaction; the maximum animation length 241 * gets multiplied by the ratio between the actual distance and this value 242 */ applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)243 public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, 244 float velocity, float maxDistance) { 245 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 246 maxDistance); 247 animator.setDuration(properties.duration); 248 animator.setInterpolator(properties.interpolator); 249 } 250 getDismissingProperties(float currValue, float endValue, float velocity, float maxDistance)251 private AnimatorProperties getDismissingProperties(float currValue, float endValue, 252 float velocity, float maxDistance) { 253 float maxLengthSeconds = (float) (mMaxLengthSeconds 254 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); 255 float diff = Math.abs(endValue - currValue); 256 float velAbs = Math.abs(velocity); 257 float y2 = calculateLinearOutFasterInY2(velAbs); 258 259 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; 260 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2); 261 float durationSeconds = startGradient * diff / velAbs; 262 if (durationSeconds <= maxLengthSeconds) { 263 mAnimatorProperties.interpolator = mLinearOutFasterIn; 264 } else if (velAbs >= mMinVelocityPxPerSecond) { 265 266 // Cross fade between linear-out-faster-in and linear interpolator with current 267 // velocity. 268 durationSeconds = maxLengthSeconds; 269 VelocityInterpolator velocityInterpolator 270 = new VelocityInterpolator(durationSeconds, velAbs, diff); 271 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 272 velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN); 273 mAnimatorProperties.interpolator = superInterpolator; 274 } else { 275 276 // Just use a normal interpolator which doesn't take the velocity into account. 277 durationSeconds = maxLengthSeconds; 278 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN; 279 } 280 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 281 return mAnimatorProperties; 282 } 283 284 /** 285 * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the 286 * velocity. The faster the velocity, the more "linear" the interpolator gets. 287 * 288 * @param velocity the velocity of the gesture. 289 * @return the y2 control point for a cubic bezier path interpolator 290 */ calculateLinearOutFasterInY2(float velocity)291 private float calculateLinearOutFasterInY2(float velocity) { 292 float t = (velocity - mMinVelocityPxPerSecond) 293 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond); 294 t = Math.max(0, Math.min(1, t)); 295 return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX; 296 } 297 298 /** 299 * @return the minimum velocity a gesture needs to have to be considered a fling 300 */ getMinVelocityPxPerSecond()301 public float getMinVelocityPxPerSecond() { 302 return mMinVelocityPxPerSecond; 303 } 304 305 /** 306 * An interpolator which interpolates two interpolators with an interpolator. 307 */ 308 private static final class InterpolatorInterpolator implements Interpolator { 309 310 private Interpolator mInterpolator1; 311 private Interpolator mInterpolator2; 312 private Interpolator mCrossfader; 313 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader)314 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, 315 Interpolator crossfader) { 316 mInterpolator1 = interpolator1; 317 mInterpolator2 = interpolator2; 318 mCrossfader = crossfader; 319 } 320 321 @Override getInterpolation(float input)322 public float getInterpolation(float input) { 323 float t = mCrossfader.getInterpolation(input); 324 return (1 - t) * mInterpolator1.getInterpolation(input) 325 + t * mInterpolator2.getInterpolation(input); 326 } 327 } 328 329 /** 330 * An interpolator which interpolates with a fixed velocity. 331 */ 332 private static final class VelocityInterpolator implements Interpolator { 333 334 private float mDurationSeconds; 335 private float mVelocity; 336 private float mDiff; 337 VelocityInterpolator(float durationSeconds, float velocity, float diff)338 private VelocityInterpolator(float durationSeconds, float velocity, float diff) { 339 mDurationSeconds = durationSeconds; 340 mVelocity = velocity; 341 mDiff = diff; 342 } 343 344 @Override getInterpolation(float input)345 public float getInterpolation(float input) { 346 float time = input * mDurationSeconds; 347 return time * mVelocity / mDiff; 348 } 349 } 350 351 private static class AnimatorProperties { 352 Interpolator interpolator; 353 long duration; 354 } 355 356 } 357