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