Home | History | Annotate | Download | only in animation
      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 package android.animation;
     17 
     18 import android.graphics.Path;
     19 import android.graphics.PointF;
     20 
     21 import java.util.ArrayList;
     22 
     23 /**
     24  * PathKeyframes relies on approximating the Path as a series of line segments.
     25  * The line segments are recursively divided until there is less than 1/2 pixel error
     26  * between the lines and the curve. Each point of the line segment is converted
     27  * to a Keyframe and a linear interpolation between Keyframes creates a good approximation
     28  * of the curve.
     29  * <p>
     30  * PathKeyframes is optimized to reduce the number of objects created when there are
     31  * many keyframes for a curve.
     32  * </p>
     33  * <p>
     34  * Typically, the returned type is a PointF, but the individual components can be extracted
     35  * as either an IntKeyframes or FloatKeyframes.
     36  * </p>
     37  */
     38 class PathKeyframes implements Keyframes {
     39     private static final int FRACTION_OFFSET = 0;
     40     private static final int X_OFFSET = 1;
     41     private static final int Y_OFFSET = 2;
     42     private static final int NUM_COMPONENTS = 3;
     43     private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>();
     44 
     45     private PointF mTempPointF = new PointF();
     46     private float[] mKeyframeData;
     47 
     48     public PathKeyframes(Path path) {
     49         this(path, 0.5f);
     50     }
     51 
     52     public PathKeyframes(Path path, float error) {
     53         if (path == null || path.isEmpty()) {
     54             throw new IllegalArgumentException("The path must not be null or empty");
     55         }
     56         mKeyframeData = path.approximate(error);
     57     }
     58 
     59     @Override
     60     public ArrayList<Keyframe> getKeyframes() {
     61         return EMPTY_KEYFRAMES;
     62     }
     63 
     64     @Override
     65     public Object getValue(float fraction) {
     66         int numPoints = mKeyframeData.length / 3;
     67         if (fraction < 0) {
     68             return interpolateInRange(fraction, 0, 1);
     69         } else if (fraction > 1) {
     70             return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
     71         } else if (fraction == 0) {
     72             return pointForIndex(0);
     73         } else if (fraction == 1) {
     74             return pointForIndex(numPoints - 1);
     75         } else {
     76             // Binary search for the correct section
     77             int low = 0;
     78             int high = numPoints - 1;
     79 
     80             while (low <= high) {
     81                 int mid = (low + high) / 2;
     82                 float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
     83 
     84                 if (fraction < midFraction) {
     85                     high = mid - 1;
     86                 } else if (fraction > midFraction) {
     87                     low = mid + 1;
     88                 } else {
     89                     return pointForIndex(mid);
     90                 }
     91             }
     92 
     93             // now high is below the fraction and low is above the fraction
     94             return interpolateInRange(fraction, high, low);
     95         }
     96     }
     97 
     98     private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
     99         int startBase = (startIndex * NUM_COMPONENTS);
    100         int endBase = (endIndex * NUM_COMPONENTS);
    101 
    102         float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
    103         float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
    104 
    105         float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
    106 
    107         float startX = mKeyframeData[startBase + X_OFFSET];
    108         float endX = mKeyframeData[endBase + X_OFFSET];
    109         float startY = mKeyframeData[startBase + Y_OFFSET];
    110         float endY = mKeyframeData[endBase + Y_OFFSET];
    111 
    112         float x = interpolate(intervalFraction, startX, endX);
    113         float y = interpolate(intervalFraction, startY, endY);
    114 
    115         mTempPointF.set(x, y);
    116         return mTempPointF;
    117     }
    118 
    119     @Override
    120     public void invalidateCache() {
    121     }
    122 
    123     @Override
    124     public void setEvaluator(TypeEvaluator evaluator) {
    125     }
    126 
    127     @Override
    128     public Class getType() {
    129         return PointF.class;
    130     }
    131 
    132     @Override
    133     public Keyframes clone() {
    134         Keyframes clone = null;
    135         try {
    136             clone = (Keyframes) super.clone();
    137         } catch (CloneNotSupportedException e) {}
    138         return clone;
    139     }
    140 
    141     private PointF pointForIndex(int index) {
    142         int base = (index * NUM_COMPONENTS);
    143         int xOffset = base + X_OFFSET;
    144         int yOffset = base + Y_OFFSET;
    145         mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
    146         return mTempPointF;
    147     }
    148 
    149     private static float interpolate(float fraction, float startValue, float endValue) {
    150         float diff = endValue - startValue;
    151         return startValue + (diff * fraction);
    152     }
    153 
    154     /**
    155      * Returns a FloatKeyframes for the X component of the Path.
    156      * @return a FloatKeyframes for the X component of the Path.
    157      */
    158     public FloatKeyframes createXFloatKeyframes() {
    159         return new FloatKeyframesBase() {
    160             @Override
    161             public float getFloatValue(float fraction) {
    162                 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
    163                 return pointF.x;
    164             }
    165         };
    166     }
    167 
    168     /**
    169      * Returns a FloatKeyframes for the Y component of the Path.
    170      * @return a FloatKeyframes for the Y component of the Path.
    171      */
    172     public FloatKeyframes createYFloatKeyframes() {
    173         return new FloatKeyframesBase() {
    174             @Override
    175             public float getFloatValue(float fraction) {
    176                 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
    177                 return pointF.y;
    178             }
    179         };
    180     }
    181 
    182     /**
    183      * Returns an IntKeyframes for the X component of the Path.
    184      * @return an IntKeyframes for the X component of the Path.
    185      */
    186     public IntKeyframes createXIntKeyframes() {
    187         return new IntKeyframesBase() {
    188             @Override
    189             public int getIntValue(float fraction) {
    190                 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
    191                 return Math.round(pointF.x);
    192             }
    193         };
    194     }
    195 
    196     /**
    197      * Returns an IntKeyframeSet for the Y component of the Path.
    198      * @return an IntKeyframeSet for the Y component of the Path.
    199      */
    200     public IntKeyframes createYIntKeyframes() {
    201         return new IntKeyframesBase() {
    202             @Override
    203             public int getIntValue(float fraction) {
    204                 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
    205                 return Math.round(pointF.y);
    206             }
    207         };
    208     }
    209 
    210     private abstract static class SimpleKeyframes implements Keyframes {
    211         @Override
    212         public void setEvaluator(TypeEvaluator evaluator) {
    213         }
    214 
    215         @Override
    216         public void invalidateCache() {
    217         }
    218 
    219         @Override
    220         public ArrayList<Keyframe> getKeyframes() {
    221             return EMPTY_KEYFRAMES;
    222         }
    223 
    224         @Override
    225         public Keyframes clone() {
    226             Keyframes clone = null;
    227             try {
    228                 clone = (Keyframes) super.clone();
    229             } catch (CloneNotSupportedException e) {}
    230             return clone;
    231         }
    232     }
    233 
    234     private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
    235         @Override
    236         public Class getType() {
    237             return Integer.class;
    238         }
    239 
    240         @Override
    241         public Object getValue(float fraction) {
    242             return getIntValue(fraction);
    243         }
    244     }
    245 
    246     private abstract static class FloatKeyframesBase extends SimpleKeyframes
    247             implements FloatKeyframes {
    248         @Override
    249         public Class getType() {
    250             return Float.class;
    251         }
    252 
    253         @Override
    254         public Object getValue(float fraction) {
    255             return getFloatValue(fraction);
    256         }
    257     }
    258 }
    259