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