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