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