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.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 
     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      */
     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      */
     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      */
    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      */
    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 
    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      */
    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      */
    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 
    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      */
    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      */
    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 
    263         InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
    264                 Interpolator crossfader) {
    265             mInterpolator1 = interpolator1;
    266             mInterpolator2 = interpolator2;
    267             mCrossfader = crossfader;
    268         }
    269 
    270         @Override
    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 
    287         private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
    288             mDurationSeconds = durationSeconds;
    289             mVelocity = velocity;
    290             mDiff = diff;
    291         }
    292 
    293         @Override
    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