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.view.ViewPropertyAnimator; 22 import android.view.animation.Interpolator; 23 import android.view.animation.PathInterpolator; 24 25 import com.android.systemui.Interpolators; 26 27 /** 28 * Utility class to calculate general fling animation when the finger is released. 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_FASTER_IN_X2 = 0.5f; 34 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f; 35 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f; 36 private static final float MIN_VELOCITY_DP_PER_SECOND = 250; 37 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; 38 39 /** 40 * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve 41 */ 42 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2; 43 44 private Interpolator mLinearOutSlowIn; 45 46 private float mMinVelocityPxPerSecond; 47 private float mMaxLengthSeconds; 48 private float mHighVelocityPxPerSecond; 49 50 private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); 51 52 public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { 53 mMaxLengthSeconds = maxLengthSeconds; 54 mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1); 55 mMinVelocityPxPerSecond 56 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 57 mHighVelocityPxPerSecond 58 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 59 } 60 61 /** 62 * Applies the interpolator and length to the animator, such that the fling animation is 63 * consistent with the finger motion. 64 * 65 * @param animator the animator to apply 66 * @param currValue the current value 67 * @param endValue the end value of the animator 68 * @param velocity the current velocity of the motion 69 */ 70 public void apply(Animator animator, float currValue, float endValue, float velocity) { 71 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); 72 } 73 74 /** 75 * Applies the interpolator and length to the animator, such that the fling animation is 76 * consistent with the finger motion. 77 * 78 * @param animator the animator to apply 79 * @param currValue the current value 80 * @param endValue the end value of the animator 81 * @param velocity the current velocity of the motion 82 */ 83 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 84 float velocity) { 85 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); 86 } 87 88 /** 89 * Applies the interpolator and length to the animator, such that the fling animation is 90 * consistent with the finger motion. 91 * 92 * @param animator the animator to apply 93 * @param currValue the current value 94 * @param endValue the end value of the animator 95 * @param velocity the current velocity of the motion 96 * @param maxDistance the maximum distance for this interaction; the maximum animation length 97 * gets multiplied by the ratio between the actual distance and this value 98 */ 99 public void apply(Animator animator, float currValue, float endValue, float velocity, 100 float maxDistance) { 101 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 102 maxDistance); 103 animator.setDuration(properties.duration); 104 animator.setInterpolator(properties.interpolator); 105 } 106 107 /** 108 * Applies the interpolator and length to the animator, such that the fling animation is 109 * consistent with the finger motion. 110 * 111 * @param animator the animator to apply 112 * @param currValue the current value 113 * @param endValue the end value of the animator 114 * @param velocity the current velocity of the motion 115 * @param maxDistance the maximum distance for this interaction; the maximum animation length 116 * gets multiplied by the ratio between the actual distance and this value 117 */ 118 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 119 float velocity, float maxDistance) { 120 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 121 maxDistance); 122 animator.setDuration(properties.duration); 123 animator.setInterpolator(properties.interpolator); 124 } 125 126 private AnimatorProperties getProperties(float currValue, 127 float endValue, float velocity, float maxDistance) { 128 float maxLengthSeconds = (float) (mMaxLengthSeconds 129 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); 130 float diff = Math.abs(endValue - currValue); 131 float velAbs = Math.abs(velocity); 132 float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs; 133 if (durationSeconds <= maxLengthSeconds) { 134 mAnimatorProperties.interpolator = mLinearOutSlowIn; 135 } else if (velAbs >= mMinVelocityPxPerSecond) { 136 137 // Cross fade between fast-out-slow-in and linear interpolator with current velocity. 138 durationSeconds = maxLengthSeconds; 139 VelocityInterpolator velocityInterpolator 140 = new VelocityInterpolator(durationSeconds, velAbs, diff); 141 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 142 velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn); 143 mAnimatorProperties.interpolator = superInterpolator; 144 } else { 145 146 // Just use a normal interpolator which doesn't take the velocity into account. 147 durationSeconds = maxLengthSeconds; 148 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN; 149 } 150 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 151 return mAnimatorProperties; 152 } 153 154 /** 155 * Applies the interpolator and length to the animator, such that the fling animation is 156 * consistent with the finger motion for the case when the animation is making something 157 * disappear. 158 * 159 * @param animator the animator to apply 160 * @param currValue the current value 161 * @param endValue the end value of the animator 162 * @param velocity the current velocity of the motion 163 * @param maxDistance the maximum distance for this interaction; the maximum animation length 164 * gets multiplied by the ratio between the actual distance and this value 165 */ 166 public void applyDismissing(Animator animator, float currValue, float endValue, 167 float velocity, float maxDistance) { 168 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 169 maxDistance); 170 animator.setDuration(properties.duration); 171 animator.setInterpolator(properties.interpolator); 172 } 173 174 /** 175 * Applies the interpolator and length to the animator, such that the fling animation is 176 * consistent with the finger motion for the case when the animation is making something 177 * disappear. 178 * 179 * @param animator the animator to apply 180 * @param currValue the current value 181 * @param endValue the end value of the animator 182 * @param velocity the current velocity of the motion 183 * @param maxDistance the maximum distance for this interaction; the maximum animation length 184 * gets multiplied by the ratio between the actual distance and this value 185 */ 186 public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, 187 float velocity, float maxDistance) { 188 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 189 maxDistance); 190 animator.setDuration(properties.duration); 191 animator.setInterpolator(properties.interpolator); 192 } 193 194 private AnimatorProperties getDismissingProperties(float currValue, float endValue, 195 float velocity, float maxDistance) { 196 float maxLengthSeconds = (float) (mMaxLengthSeconds 197 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); 198 float diff = Math.abs(endValue - currValue); 199 float velAbs = Math.abs(velocity); 200 float y2 = calculateLinearOutFasterInY2(velAbs); 201 202 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; 203 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2); 204 float durationSeconds = startGradient * diff / velAbs; 205 if (durationSeconds <= maxLengthSeconds) { 206 mAnimatorProperties.interpolator = mLinearOutFasterIn; 207 } else if (velAbs >= mMinVelocityPxPerSecond) { 208 209 // Cross fade between linear-out-faster-in and linear interpolator with current 210 // velocity. 211 durationSeconds = maxLengthSeconds; 212 VelocityInterpolator velocityInterpolator 213 = new VelocityInterpolator(durationSeconds, velAbs, diff); 214 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 215 velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn); 216 mAnimatorProperties.interpolator = superInterpolator; 217 } else { 218 219 // Just use a normal interpolator which doesn't take the velocity into account. 220 durationSeconds = maxLengthSeconds; 221 mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN; 222 } 223 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 224 return mAnimatorProperties; 225 } 226 227 /** 228 * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the 229 * velocity. The faster the velocity, the more "linear" the interpolator gets. 230 * 231 * @param velocity the velocity of the gesture. 232 * @return the y2 control point for a cubic bezier path interpolator 233 */ 234 private float calculateLinearOutFasterInY2(float velocity) { 235 float t = (velocity - mMinVelocityPxPerSecond) 236 / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond); 237 t = Math.max(0, Math.min(1, t)); 238 return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX; 239 } 240 241 /** 242 * @return the minimum velocity a gesture needs to have to be considered a fling 243 */ 244 public float getMinVelocityPxPerSecond() { 245 return mMinVelocityPxPerSecond; 246 } 247 248 /** 249 * An interpolator which interpolates two interpolators with an interpolator. 250 */ 251 private static final class InterpolatorInterpolator implements Interpolator { 252 253 private Interpolator mInterpolator1; 254 private Interpolator mInterpolator2; 255 private Interpolator mCrossfader; 256 257 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, 258 Interpolator crossfader) { 259 mInterpolator1 = interpolator1; 260 mInterpolator2 = interpolator2; 261 mCrossfader = crossfader; 262 } 263 264 @Override 265 public float getInterpolation(float input) { 266 float t = mCrossfader.getInterpolation(input); 267 return (1 - t) * mInterpolator1.getInterpolation(input) 268 + t * mInterpolator2.getInterpolation(input); 269 } 270 } 271 272 /** 273 * An interpolator which interpolates with a fixed velocity. 274 */ 275 private static final class VelocityInterpolator implements Interpolator { 276 277 private float mDurationSeconds; 278 private float mVelocity; 279 private float mDiff; 280 281 private VelocityInterpolator(float durationSeconds, float velocity, float diff) { 282 mDurationSeconds = durationSeconds; 283 mVelocity = velocity; 284 mDiff = diff; 285 } 286 287 @Override 288 public float getInterpolation(float input) { 289 float time = input * mDurationSeconds; 290 return time * mVelocity / mDiff; 291 } 292 } 293 294 private static class AnimatorProperties { 295 Interpolator interpolator; 296 long duration; 297 } 298 299 } 300