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.AnimationUtils; 23 import android.view.animation.Interpolator; 24 import android.view.animation.PathInterpolator; 25 26 /** 27 * Utility class to calculate general fling animation when the finger is released. 28 */ 29 public class FlingAnimationUtils { 30 31 private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f; 32 private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f; 33 private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f; 34 private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f; 35 private static final float MIN_VELOCITY_DP_PER_SECOND = 250; 36 private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; 37 38 /** 39 * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve 40 */ 41 private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2; 42 43 private Interpolator mLinearOutSlowIn; 44 private Interpolator mFastOutSlowIn; 45 private Interpolator mFastOutLinearIn; 46 47 private float mMinVelocityPxPerSecond; 48 private float mMaxLengthSeconds; 49 private float mHighVelocityPxPerSecond; 50 51 private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); 52 FlingAnimationUtils(Context ctx, float maxLengthSeconds)53 public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { 54 mMaxLengthSeconds = maxLengthSeconds; 55 mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1); 56 mFastOutSlowIn 57 = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in); 58 mFastOutLinearIn 59 = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_linear_in); 60 mMinVelocityPxPerSecond 61 = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 62 mHighVelocityPxPerSecond 63 = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; 64 } 65 66 /** 67 * Applies the interpolator and length to the animator, such that the fling animation is 68 * consistent with the finger motion. 69 * 70 * @param animator the animator to apply 71 * @param currValue the current value 72 * @param endValue the end value of the animator 73 * @param velocity the current velocity of the motion 74 */ apply(Animator animator, float currValue, float endValue, float velocity)75 public void apply(Animator animator, float currValue, float endValue, float velocity) { 76 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); 77 } 78 79 /** 80 * Applies the interpolator and length to the animator, such that the fling animation is 81 * consistent with the finger motion. 82 * 83 * @param animator the animator to apply 84 * @param currValue the current value 85 * @param endValue the end value of the animator 86 * @param velocity the current velocity of the motion 87 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity)88 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 89 float velocity) { 90 apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); 91 } 92 93 /** 94 * Applies the interpolator and length to the animator, such that the fling animation is 95 * consistent with the finger motion. 96 * 97 * @param animator the animator to apply 98 * @param currValue the current value 99 * @param endValue the end value of the animator 100 * @param velocity the current velocity of the motion 101 * @param maxDistance the maximum distance for this interaction; the maximum animation length 102 * gets multiplied by the ratio between the actual distance and this value 103 */ apply(Animator animator, float currValue, float endValue, float velocity, float maxDistance)104 public void apply(Animator animator, float currValue, float endValue, float velocity, 105 float maxDistance) { 106 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 107 maxDistance); 108 animator.setDuration(properties.duration); 109 animator.setInterpolator(properties.interpolator); 110 } 111 112 /** 113 * Applies the interpolator and length to the animator, such that the fling animation is 114 * consistent with the finger motion. 115 * 116 * @param animator the animator to apply 117 * @param currValue the current value 118 * @param endValue the end value of the animator 119 * @param velocity the current velocity of the motion 120 * @param maxDistance the maximum distance for this interaction; the maximum animation length 121 * gets multiplied by the ratio between the actual distance and this value 122 */ apply(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)123 public void apply(ViewPropertyAnimator animator, float currValue, float endValue, 124 float velocity, float maxDistance) { 125 AnimatorProperties properties = getProperties(currValue, endValue, velocity, 126 maxDistance); 127 animator.setDuration(properties.duration); 128 animator.setInterpolator(properties.interpolator); 129 } 130 getProperties(float currValue, float endValue, float velocity, float maxDistance)131 private AnimatorProperties getProperties(float currValue, 132 float endValue, float velocity, float maxDistance) { 133 float maxLengthSeconds = (float) (mMaxLengthSeconds 134 * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); 135 float diff = Math.abs(endValue - currValue); 136 float velAbs = Math.abs(velocity); 137 float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs; 138 if (durationSeconds <= maxLengthSeconds) { 139 mAnimatorProperties.interpolator = mLinearOutSlowIn; 140 } else if (velAbs >= mMinVelocityPxPerSecond) { 141 142 // Cross fade between fast-out-slow-in and linear interpolator with current velocity. 143 durationSeconds = maxLengthSeconds; 144 VelocityInterpolator velocityInterpolator 145 = new VelocityInterpolator(durationSeconds, velAbs, diff); 146 InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( 147 velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn); 148 mAnimatorProperties.interpolator = superInterpolator; 149 } else { 150 151 // Just use a normal interpolator which doesn't take the velocity into account. 152 durationSeconds = maxLengthSeconds; 153 mAnimatorProperties.interpolator = mFastOutSlowIn; 154 } 155 mAnimatorProperties.duration = (long) (durationSeconds * 1000); 156 return mAnimatorProperties; 157 } 158 159 /** 160 * Applies the interpolator and length to the animator, such that the fling animation is 161 * consistent with the finger motion for the case when the animation is making something 162 * disappear. 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 */ applyDismissing(Animator animator, float currValue, float endValue, float velocity, float maxDistance)171 public void applyDismissing(Animator animator, float currValue, float endValue, 172 float velocity, float maxDistance) { 173 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 174 maxDistance); 175 animator.setDuration(properties.duration); 176 animator.setInterpolator(properties.interpolator); 177 } 178 179 /** 180 * Applies the interpolator and length to the animator, such that the fling animation is 181 * consistent with the finger motion for the case when the animation is making something 182 * disappear. 183 * 184 * @param animator the animator to apply 185 * @param currValue the current value 186 * @param endValue the end value of the animator 187 * @param velocity the current velocity of the motion 188 * @param maxDistance the maximum distance for this interaction; the maximum animation length 189 * gets multiplied by the ratio between the actual distance and this value 190 */ applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, float velocity, float maxDistance)191 public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, 192 float velocity, float maxDistance) { 193 AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, 194 maxDistance); 195 animator.setDuration(properties.duration); 196 animator.setInterpolator(properties.interpolator); 197 } 198 getDismissingProperties(float currValue, float endValue, float velocity, float maxDistance)199 private AnimatorProperties getDismissingProperties(float currValue, float endValue, 200 float velocity, float maxDistance) { 201 float maxLengthSeconds = (float) (mMaxLengthSeconds 202 * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); 203 float diff = Math.abs(endValue - currValue); 204 float velAbs = Math.abs(velocity); 205 float y2 = calculateLinearOutFasterInY2(velAbs); 206 207 float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; 208 Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, 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 */ calculateLinearOutFasterInY2(float velocity)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 */ getMinVelocityPxPerSecond()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 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader)262 InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, 263 Interpolator crossfader) { 264 mInterpolator1 = interpolator1; 265 mInterpolator2 = interpolator2; 266 mCrossfader = crossfader; 267 } 268 269 @Override getInterpolation(float input)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 VelocityInterpolator(float durationSeconds, float velocity, float diff)286 private VelocityInterpolator(float durationSeconds, float velocity, float diff) { 287 mDurationSeconds = durationSeconds; 288 mVelocity = velocity; 289 mDiff = diff; 290 } 291 292 @Override getInterpolation(float input)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