Home | History | Annotate | Download | only in statusbar
      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 
     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      */
     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      */
     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      */
    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      */
    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) {
    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      */
    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      */
    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) {
    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      */
    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