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