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