Home | History | Annotate | Download | only in animation
      1 /*
      2  * Copyright (C) 2013 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.view.animation;
     17 
     18 import android.content.Context;
     19 import android.content.res.Resources;
     20 import android.content.res.Resources.Theme;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Path;
     23 import android.util.AttributeSet;
     24 import android.util.PathParser;
     25 import android.view.InflateException;
     26 
     27 import com.android.internal.R;
     28 
     29 /**
     30  * An interpolator that can traverse a Path that extends from <code>Point</code>
     31  * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code>
     32  * is the input value and the output is the y coordinate of the line at that point.
     33  * This means that the Path must conform to a function <code>y = f(x)</code>.
     34  *
     35  * <p>The <code>Path</code> must not have gaps in the x direction and must not
     36  * loop back on itself such that there can be two points sharing the same x coordinate.
     37  * It is alright to have a disjoint line in the vertical direction:</p>
     38  * <p><blockquote><pre>
     39  *     Path path = new Path();
     40  *     path.lineTo(0.25f, 0.25f);
     41  *     path.moveTo(0.25f, 0.5f);
     42  *     path.lineTo(1f, 1f);
     43  * </pre></blockquote></p>
     44  */
     45 public class PathInterpolator implements Interpolator {
     46 
     47     // This governs how accurate the approximation of the Path is.
     48     private static final float PRECISION = 0.002f;
     49 
     50     private float[] mX; // x coordinates in the line
     51 
     52     private float[] mY; // y coordinates in the line
     53 
     54     /**
     55      * Create an interpolator for an arbitrary <code>Path</code>. The <code>Path</code>
     56      * must begin at <code>(0, 0)</code> and end at <code>(1, 1)</code>.
     57      *
     58      * @param path The <code>Path</code> to use to make the line representing the interpolator.
     59      */
     60     public PathInterpolator(Path path) {
     61         initPath(path);
     62     }
     63 
     64     /**
     65      * Create an interpolator for a quadratic Bezier curve. The end points
     66      * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
     67      *
     68      * @param controlX The x coordinate of the quadratic Bezier control point.
     69      * @param controlY The y coordinate of the quadratic Bezier control point.
     70      */
     71     public PathInterpolator(float controlX, float controlY) {
     72         initQuad(controlX, controlY);
     73     }
     74 
     75     /**
     76      * Create an interpolator for a cubic Bezier curve.  The end points
     77      * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
     78      *
     79      * @param controlX1 The x coordinate of the first control point of the cubic Bezier.
     80      * @param controlY1 The y coordinate of the first control point of the cubic Bezier.
     81      * @param controlX2 The x coordinate of the second control point of the cubic Bezier.
     82      * @param controlY2 The y coordinate of the second control point of the cubic Bezier.
     83      */
     84     public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
     85         initCubic(controlX1, controlY1, controlX2, controlY2);
     86     }
     87 
     88     public PathInterpolator(Context context, AttributeSet attrs) {
     89         this(context.getResources(), context.getTheme(), attrs);
     90     }
     91 
     92     /** @hide */
     93     public PathInterpolator(Resources res, Theme theme, AttributeSet attrs) {
     94         TypedArray a;
     95         if (theme != null) {
     96             a = theme.obtainStyledAttributes(attrs, R.styleable.PathInterpolator, 0, 0);
     97         } else {
     98             a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
     99         }
    100         parseInterpolatorFromTypeArray(a);
    101 
    102         a.recycle();
    103     }
    104 
    105     private void parseInterpolatorFromTypeArray(TypedArray a) {
    106         // If there is pathData defined in the xml file, then the controls points
    107         // will be all coming from pathData.
    108         if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
    109             String pathData = a.getString(R.styleable.PathInterpolator_pathData);
    110             Path path = PathParser.createPathFromPathData(pathData);
    111             if (path == null) {
    112                 throw new InflateException("The path is null, which is created"
    113                         + " from " + pathData);
    114             }
    115             initPath(path);
    116         } else {
    117             if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
    118                 throw new InflateException("pathInterpolator requires the controlX1 attribute");
    119             } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
    120                 throw new InflateException("pathInterpolator requires the controlY1 attribute");
    121             }
    122             float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
    123             float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
    124 
    125             boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
    126             boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
    127 
    128             if (hasX2 != hasY2) {
    129                 throw new InflateException(
    130                         "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
    131             }
    132 
    133             if (!hasX2) {
    134                 initQuad(x1, y1);
    135             } else {
    136                 float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
    137                 float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
    138                 initCubic(x1, y1, x2, y2);
    139             }
    140         }
    141     }
    142 
    143     private void initQuad(float controlX, float controlY) {
    144         Path path = new Path();
    145         path.moveTo(0, 0);
    146         path.quadTo(controlX, controlY, 1f, 1f);
    147         initPath(path);
    148     }
    149 
    150     private void initCubic(float x1, float y1, float x2, float y2) {
    151         Path path = new Path();
    152         path.moveTo(0, 0);
    153         path.cubicTo(x1, y1, x2, y2, 1f, 1f);
    154         initPath(path);
    155     }
    156 
    157     private void initPath(Path path) {
    158         float[] pointComponents = path.approximate(PRECISION);
    159 
    160         int numPoints = pointComponents.length / 3;
    161         if (pointComponents[1] != 0 || pointComponents[2] != 0
    162                 || pointComponents[pointComponents.length - 2] != 1
    163                 || pointComponents[pointComponents.length - 1] != 1) {
    164             throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
    165         }
    166 
    167         mX = new float[numPoints];
    168         mY = new float[numPoints];
    169         float prevX = 0;
    170         float prevFraction = 0;
    171         int componentIndex = 0;
    172         for (int i = 0; i < numPoints; i++) {
    173             float fraction = pointComponents[componentIndex++];
    174             float x = pointComponents[componentIndex++];
    175             float y = pointComponents[componentIndex++];
    176             if (fraction == prevFraction && x != prevX) {
    177                 throw new IllegalArgumentException(
    178                         "The Path cannot have discontinuity in the X axis.");
    179             }
    180             if (x < prevX) {
    181                 throw new IllegalArgumentException("The Path cannot loop back on itself.");
    182             }
    183             mX[i] = x;
    184             mY[i] = y;
    185             prevX = x;
    186             prevFraction = fraction;
    187         }
    188     }
    189 
    190     /**
    191      * Using the line in the Path in this interpolator that can be described as
    192      * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
    193      * as the x coordinate. Values less than 0 will always return 0 and values greater
    194      * than 1 will always return 1.
    195      *
    196      * @param t Treated as the x coordinate along the line.
    197      * @return The y coordinate of the Path along the line where x = <code>t</code>.
    198      * @see Interpolator#getInterpolation(float)
    199      */
    200     @Override
    201     public float getInterpolation(float t) {
    202         if (t <= 0) {
    203             return 0;
    204         } else if (t >= 1) {
    205             return 1;
    206         }
    207         // Do a binary search for the correct x to interpolate between.
    208         int startIndex = 0;
    209         int endIndex = mX.length - 1;
    210 
    211         while (endIndex - startIndex > 1) {
    212             int midIndex = (startIndex + endIndex) / 2;
    213             if (t < mX[midIndex]) {
    214                 endIndex = midIndex;
    215             } else {
    216                 startIndex = midIndex;
    217             }
    218         }
    219 
    220         float xRange = mX[endIndex] - mX[startIndex];
    221         if (xRange == 0) {
    222             return mY[startIndex];
    223         }
    224 
    225         float tInRange = t - mX[startIndex];
    226         float fraction = tInRange / xRange;
    227 
    228         float startY = mY[startIndex];
    229         float endY = mY[endIndex];
    230         return startY + (fraction * (endY - startY));
    231     }
    232 }
    233