Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 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 | |
Jorim Jaggi | b6cdcbc | 2014-07-25 21:58:29 +0200 | [diff] [blame] | 19 | import android.animation.Animator; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 20 | import android.content.Context; |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 21 | import android.view.ViewPropertyAnimator; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 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 | |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 31 | private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f; |
Jorim Jaggi | 2580a976 | 2014-06-25 03:08:25 +0200 | [diff] [blame] | 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; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 35 | private static final float MIN_VELOCITY_DP_PER_SECOND = 250; |
Jorim Jaggi | efbd7e3 | 2014-05-21 17:14:18 +0200 | [diff] [blame] | 36 | private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 37 | |
| 38 | /** |
| 39 | * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve |
| 40 | */ |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 41 | private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 42 | |
| 43 | private Interpolator mLinearOutSlowIn; |
| 44 | private Interpolator mFastOutSlowIn; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 45 | private Interpolator mFastOutLinearIn; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 46 | |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 47 | private float mMinVelocityPxPerSecond; |
| 48 | private float mMaxLengthSeconds; |
Jorim Jaggi | efbd7e3 | 2014-05-21 17:14:18 +0200 | [diff] [blame] | 49 | private float mHighVelocityPxPerSecond; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 50 | |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 51 | private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); |
| 52 | |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 53 | public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { |
| 54 | mMaxLengthSeconds = maxLengthSeconds; |
| 55 | mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1); |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 56 | mFastOutSlowIn |
| 57 | = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in); |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 58 | mFastOutLinearIn |
| 59 | = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_linear_in); |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 60 | mMinVelocityPxPerSecond |
| 61 | = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; |
Jorim Jaggi | efbd7e3 | 2014-05-21 17:14:18 +0200 | [diff] [blame] | 62 | mHighVelocityPxPerSecond |
| 63 | = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 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 | */ |
Jorim Jaggi | b6cdcbc | 2014-07-25 21:58:29 +0200 | [diff] [blame] | 75 | public void apply(Animator animator, float currValue, float endValue, float velocity) { |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 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 |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 87 | */ |
| 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 |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 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 | */ |
Jorim Jaggi | b6cdcbc | 2014-07-25 21:58:29 +0200 | [diff] [blame] | 104 | public void apply(Animator animator, float currValue, float endValue, float velocity, |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 105 | float maxDistance) { |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 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 | */ |
| 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 | |
| 131 | private AnimatorProperties getProperties(float currValue, |
| 132 | float endValue, float velocity, float maxDistance) { |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 133 | float maxLengthSeconds = (float) (mMaxLengthSeconds |
| 134 | * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 135 | float diff = Math.abs(endValue - currValue); |
| 136 | float velAbs = Math.abs(velocity); |
| 137 | float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 138 | if (durationSeconds <= maxLengthSeconds) { |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 139 | mAnimatorProperties.interpolator = mLinearOutSlowIn; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 140 | } else if (velAbs >= mMinVelocityPxPerSecond) { |
| 141 | |
| 142 | // Cross fade between fast-out-slow-in and linear interpolator with current velocity. |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 143 | durationSeconds = maxLengthSeconds; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 144 | VelocityInterpolator velocityInterpolator |
| 145 | = new VelocityInterpolator(durationSeconds, velAbs, diff); |
| 146 | InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( |
| 147 | velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn); |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 148 | mAnimatorProperties.interpolator = superInterpolator; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 149 | } else { |
| 150 | |
| 151 | // Just use a normal interpolator which doesn't take the velocity into account. |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 152 | durationSeconds = maxLengthSeconds; |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 153 | mAnimatorProperties.interpolator = mFastOutSlowIn; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 154 | } |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 155 | mAnimatorProperties.duration = (long) (durationSeconds * 1000); |
| 156 | return mAnimatorProperties; |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | /** |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 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 | */ |
Jorim Jaggi | b6cdcbc | 2014-07-25 21:58:29 +0200 | [diff] [blame] | 171 | public void applyDismissing(Animator animator, float currValue, float endValue, |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 172 | float velocity, float maxDistance) { |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 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 | */ |
| 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 | |
| 199 | private AnimatorProperties getDismissingProperties(float currValue, float endValue, |
| 200 | float velocity, float maxDistance) { |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 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); |
Jorim Jaggi | efbd7e3 | 2014-05-21 17:14:18 +0200 | [diff] [blame] | 205 | float y2 = calculateLinearOutFasterInY2(velAbs); |
| 206 | |
Jorim Jaggi | 2580a976 | 2014-06-25 03:08:25 +0200 | [diff] [blame] | 207 | float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; |
| 208 | Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2); |
Jorim Jaggi | efbd7e3 | 2014-05-21 17:14:18 +0200 | [diff] [blame] | 209 | float durationSeconds = startGradient * diff / velAbs; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 210 | if (durationSeconds <= maxLengthSeconds) { |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 211 | mAnimatorProperties.interpolator = mLinearOutFasterIn; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 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); |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 221 | mAnimatorProperties.interpolator = superInterpolator; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 222 | } else { |
| 223 | |
| 224 | // Just use a normal interpolator which doesn't take the velocity into account. |
| 225 | durationSeconds = maxLengthSeconds; |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 226 | mAnimatorProperties.interpolator = mFastOutLinearIn; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 227 | } |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 228 | mAnimatorProperties.duration = (long) (durationSeconds * 1000); |
| 229 | return mAnimatorProperties; |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 230 | } |
| 231 | |
| 232 | /** |
Jorim Jaggi | efbd7e3 | 2014-05-21 17:14:18 +0200 | [diff] [blame] | 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 | /** |
Jorim Jaggi | 1d48069 | 2014-05-20 19:41:58 +0200 | [diff] [blame] | 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 | /** |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 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 | } |
Selim Cinek | 4c6969a | 2014-05-26 19:22:17 +0200 | [diff] [blame] | 298 | |
| 299 | private static class AnimatorProperties { |
| 300 | Interpolator interpolator; |
| 301 | long duration; |
| 302 | } |
| 303 | |
Jorim Jaggi | 87cd5e7 | 2014-05-12 23:29:10 +0200 | [diff] [blame] | 304 | } |