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 */ PathInterpolator(Path path)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 */ PathInterpolator(float controlX, float controlY)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 */ PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2)84 public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) { 85 initCubic(controlX1, controlY1, controlX2, controlY2); 86 } 87 PathInterpolator(Context context, AttributeSet attrs)88 public PathInterpolator(Context context, AttributeSet attrs) { 89 this(context.getResources(), context.getTheme(), attrs); 90 } 91 92 /** @hide */ PathInterpolator(Resources res, Theme theme, AttributeSet attrs)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 parseInterpolatorFromTypeArray(TypedArray a)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 initQuad(float controlX, float controlY)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 initCubic(float x1, float y1, float x2, float y2)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 initPath(Path path)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 getInterpolation(float t)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