Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2016 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.incallui.answer.impl.utils;
     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 /** Utility class to calculate general fling animation when the finger is released. */
     26 public class FlingAnimationUtils {
     27 
     28   private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
     29   private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
     30   private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
     31   private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
     32   private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
     33   private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
     34 
     35   /** Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve */
     36   private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2;
     37 
     38   private Interpolator linearOutSlowIn;
     39 
     40   private float minVelocityPxPerSecond;
     41   private float maxLengthSeconds;
     42   private float highVelocityPxPerSecond;
     43 
     44   private AnimatorProperties animatorProperties = new AnimatorProperties();
     45 
     46   public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
     47     this.maxLengthSeconds = maxLengthSeconds;
     48     linearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1);
     49     minVelocityPxPerSecond =
     50         MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
     51     highVelocityPxPerSecond =
     52         HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
     53   }
     54 
     55   /**
     56    * Applies the interpolator and length to the animator, such that the fling animation is
     57    * consistent with the finger motion.
     58    *
     59    * @param animator the animator to apply
     60    * @param currValue the current value
     61    * @param endValue the end value of the animator
     62    * @param velocity the current velocity of the motion
     63    */
     64   public void apply(Animator animator, float currValue, float endValue, float velocity) {
     65     apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
     66   }
     67 
     68   /**
     69    * Applies the interpolator and length to the animator, such that the fling animation is
     70    * consistent with the finger motion.
     71    *
     72    * @param animator the animator to apply
     73    * @param currValue the current value
     74    * @param endValue the end value of the animator
     75    * @param velocity the current velocity of the motion
     76    */
     77   public void apply(
     78       ViewPropertyAnimator animator, float currValue, float endValue, float velocity) {
     79     apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
     80   }
     81 
     82   /**
     83    * Applies the interpolator and length to the animator, such that the fling animation is
     84    * consistent with the finger motion.
     85    *
     86    * @param animator the animator to apply
     87    * @param currValue the current value
     88    * @param endValue the end value of the animator
     89    * @param velocity the current velocity of the motion
     90    * @param maxDistance the maximum distance for this interaction; the maximum animation length gets
     91    *     multiplied by the ratio between the actual distance and this value
     92    */
     93   public void apply(
     94       Animator animator, float currValue, float endValue, float velocity, float maxDistance) {
     95     AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance);
     96     animator.setDuration(properties.duration);
     97     animator.setInterpolator(properties.interpolator);
     98   }
     99 
    100   /**
    101    * Applies the interpolator and length to the animator, such that the fling animation is
    102    * consistent with the finger motion.
    103    *
    104    * @param animator the animator to apply
    105    * @param currValue the current value
    106    * @param endValue the end value of the animator
    107    * @param velocity the current velocity of the motion
    108    * @param maxDistance the maximum distance for this interaction; the maximum animation length gets
    109    *     multiplied by the ratio between the actual distance and this value
    110    */
    111   public void apply(
    112       ViewPropertyAnimator animator,
    113       float currValue,
    114       float endValue,
    115       float velocity,
    116       float maxDistance) {
    117     AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance);
    118     animator.setDuration(properties.duration);
    119     animator.setInterpolator(properties.interpolator);
    120   }
    121 
    122   private AnimatorProperties getProperties(
    123       float currValue, float endValue, float velocity, float maxDistance) {
    124     float maxLengthSeconds =
    125         (float) (this.maxLengthSeconds * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
    126     float diff = Math.abs(endValue - currValue);
    127     float velAbs = Math.abs(velocity);
    128     float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs;
    129     if (durationSeconds <= maxLengthSeconds) {
    130       animatorProperties.interpolator = linearOutSlowIn;
    131     } else if (velAbs >= minVelocityPxPerSecond) {
    132 
    133       // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
    134       durationSeconds = maxLengthSeconds;
    135       VelocityInterpolator velocityInterpolator =
    136           new VelocityInterpolator(durationSeconds, velAbs, diff);
    137       animatorProperties.interpolator =
    138           new InterpolatorInterpolator(velocityInterpolator, linearOutSlowIn, linearOutSlowIn);
    139     } else {
    140 
    141       // Just use a normal interpolator which doesn't take the velocity into account.
    142       durationSeconds = maxLengthSeconds;
    143       animatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN;
    144     }
    145     animatorProperties.duration = (long) (durationSeconds * 1000);
    146     return animatorProperties;
    147   }
    148 
    149   /**
    150    * Applies the interpolator and length to the animator, such that the fling animation is
    151    * consistent with the finger motion for the case when the animation is making something
    152    * disappear.
    153    *
    154    * @param animator the animator to apply
    155    * @param currValue the current value
    156    * @param endValue the end value of the animator
    157    * @param velocity the current velocity of the motion
    158    * @param maxDistance the maximum distance for this interaction; the maximum animation length gets
    159    *     multiplied by the ratio between the actual distance and this value
    160    */
    161   public void applyDismissing(
    162       Animator animator, float currValue, float endValue, float velocity, float maxDistance) {
    163     AnimatorProperties properties =
    164         getDismissingProperties(currValue, endValue, velocity, maxDistance);
    165     animator.setDuration(properties.duration);
    166     animator.setInterpolator(properties.interpolator);
    167   }
    168 
    169   /**
    170    * Applies the interpolator and length to the animator, such that the fling animation is
    171    * consistent with the finger motion for the case when the animation is making something
    172    * disappear.
    173    *
    174    * @param animator the animator to apply
    175    * @param currValue the current value
    176    * @param endValue the end value of the animator
    177    * @param velocity the current velocity of the motion
    178    * @param maxDistance the maximum distance for this interaction; the maximum animation length gets
    179    *     multiplied by the ratio between the actual distance and this value
    180    */
    181   public void applyDismissing(
    182       ViewPropertyAnimator animator,
    183       float currValue,
    184       float endValue,
    185       float velocity,
    186       float maxDistance) {
    187     AnimatorProperties properties =
    188         getDismissingProperties(currValue, endValue, velocity, maxDistance);
    189     animator.setDuration(properties.duration);
    190     animator.setInterpolator(properties.interpolator);
    191   }
    192 
    193   private AnimatorProperties getDismissingProperties(
    194       float currValue, float endValue, float velocity, float maxDistance) {
    195     float maxLengthSeconds =
    196         (float)
    197             (this.maxLengthSeconds * 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       animatorProperties.interpolator = mLinearOutFasterIn;
    207     } else if (velAbs >= minVelocityPxPerSecond) {
    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 =
    215           new InterpolatorInterpolator(velocityInterpolator, mLinearOutFasterIn, linearOutSlowIn);
    216       animatorProperties.interpolator = superInterpolator;
    217     } else {
    218 
    219       // Just use a normal interpolator which doesn't take the velocity into account.
    220       durationSeconds = maxLengthSeconds;
    221       animatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN;
    222     }
    223     animatorProperties.duration = (long) (durationSeconds * 1000);
    224     return animatorProperties;
    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 =
    236         (velocity - minVelocityPxPerSecond) / (highVelocityPxPerSecond - minVelocityPxPerSecond);
    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   /** @return the minimum velocity a gesture needs to have to be considered a fling */
    242   public float getMinVelocityPxPerSecond() {
    243     return minVelocityPxPerSecond;
    244   }
    245 
    246   /** An interpolator which interpolates two interpolators with an interpolator. */
    247   private static final class InterpolatorInterpolator implements Interpolator {
    248 
    249     private Interpolator interpolator1;
    250     private Interpolator interpolator2;
    251     private Interpolator crossfader;
    252 
    253     InterpolatorInterpolator(
    254         Interpolator interpolator1, Interpolator interpolator2, Interpolator crossfader) {
    255       this.interpolator1 = interpolator1;
    256       this.interpolator2 = interpolator2;
    257       this.crossfader = crossfader;
    258     }
    259 
    260     @Override
    261     public float getInterpolation(float input) {
    262       float t = crossfader.getInterpolation(input);
    263       return (1 - t) * interpolator1.getInterpolation(input)
    264           + t * interpolator2.getInterpolation(input);
    265     }
    266   }
    267 
    268   /** An interpolator which interpolates with a fixed velocity. */
    269   private static final class VelocityInterpolator implements Interpolator {
    270 
    271     private float durationSeconds;
    272     private float velocity;
    273     private float diff;
    274 
    275     private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
    276       this.durationSeconds = durationSeconds;
    277       this.velocity = velocity;
    278       this.diff = diff;
    279     }
    280 
    281     @Override
    282     public float getInterpolation(float input) {
    283       float time = input * durationSeconds;
    284       return time * velocity / diff;
    285     }
    286   }
    287 
    288   private static class AnimatorProperties {
    289 
    290     Interpolator interpolator;
    291     long duration;
    292   }
    293 }
    294