1b77813a5dbce67e7ecb457e39900b82f04def8beztenghui/* 2b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * Copyright (C) 2017 The Android Open Source Project 3b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * 4b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * Licensed under the Apache License, Version 2.0 (the "License"); 5b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * you may not use this file except in compliance with the License. 6b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * You may obtain a copy of the License at 7b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * 8b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * http://www.apache.org/licenses/LICENSE-2.0 9b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * 10b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * Unless required by applicable law or agreed to in writing, software 11b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * distributed under the License is distributed on an "AS IS" BASIS, 12b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * See the License for the specific language governing permissions and 14b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * limitations under the License. 15b77813a5dbce67e7ecb457e39900b82f04def8beztenghui */ 16b77813a5dbce67e7ecb457e39900b82f04def8beztenghuipackage android.support.graphics.drawable; 17b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 18b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 20b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport static java.lang.Math.abs; 21b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport static java.lang.Math.min; 22b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 23b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.content.Context; 24b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.content.res.Resources; 25b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.content.res.TypedArray; 26b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.graphics.Path; 27b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.graphics.PathMeasure; 28b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.support.annotation.RestrictTo; 29b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.support.v4.content.res.TypedArrayUtils; 30142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport android.support.v4.graphics.PathParser; 31b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.util.AttributeSet; 32b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.view.InflateException; 33b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport android.view.animation.Interpolator; 34b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 35b77813a5dbce67e7ecb457e39900b82f04def8beztenghuiimport org.xmlpull.v1.XmlPullParser; 36b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 37b77813a5dbce67e7ecb457e39900b82f04def8beztenghui/** 38b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * An interpolator that can traverse a Path that extends from <code>Point</code> 39b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code> 40b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * is the input value and the output is the y coordinate of the line at that point. 41b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * This means that the Path must conform to a function <code>y = f(x)</code>. 42b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * 43b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * <p>The <code>Path</code> must not have gaps in the x direction and must not 44b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * loop back on itself such that there can be two points sharing the same x coordinate. 45b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * It is alright to have a disjoint line in the vertical direction:</p> 46b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * <p><blockquote><pre> 47b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * Path path = new Path(); 48b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * path.lineTo(0.25f, 0.25f); 49b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * path.moveTo(0.25f, 0.5f); 50b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * path.lineTo(1f, 1f); 51b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * </pre></blockquote></p> 52b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * @hide 53b77813a5dbce67e7ecb457e39900b82f04def8beztenghui */ 54b77813a5dbce67e7ecb457e39900b82f04def8beztenghui@RestrictTo(LIBRARY_GROUP) 55b77813a5dbce67e7ecb457e39900b82f04def8beztenghuipublic class PathInterpolatorCompat implements Interpolator { 56b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 57b77813a5dbce67e7ecb457e39900b82f04def8beztenghui // This governs how accurate the approximation of the Path is. 58b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private static final float PRECISION = 0.002f; 59b77813a5dbce67e7ecb457e39900b82f04def8beztenghui public static final int MAX_NUM_POINTS = 3000; 60b77813a5dbce67e7ecb457e39900b82f04def8beztenghui public static final double EPSILON = 0.00001; 61b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 62b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private float[] mX; // x coordinates in the line 63b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 64b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private float[] mY; // y coordinates in the line 65b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 66b77813a5dbce67e7ecb457e39900b82f04def8beztenghui public PathInterpolatorCompat(Context context, AttributeSet attrs, XmlPullParser parser) { 67b77813a5dbce67e7ecb457e39900b82f04def8beztenghui this(context.getResources(), context.getTheme(), attrs, parser); 68b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 69b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 70b77813a5dbce67e7ecb457e39900b82f04def8beztenghui public PathInterpolatorCompat(Resources res, Resources.Theme theme, AttributeSet attrs, 71b77813a5dbce67e7ecb457e39900b82f04def8beztenghui XmlPullParser parser) { 72b77813a5dbce67e7ecb457e39900b82f04def8beztenghui TypedArray a = TypedArrayUtils.obtainAttributes(res, theme, 73b77813a5dbce67e7ecb457e39900b82f04def8beztenghui attrs, AndroidResources.STYLEABLE_PATH_INTERPOLATOR); 74b77813a5dbce67e7ecb457e39900b82f04def8beztenghui parseInterpolatorFromTypeArray(a, parser); 75b77813a5dbce67e7ecb457e39900b82f04def8beztenghui a.recycle(); 76b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 77b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 78b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private void parseInterpolatorFromTypeArray(TypedArray a, XmlPullParser parser) { 79b77813a5dbce67e7ecb457e39900b82f04def8beztenghui // If there is pathData defined in the xml file, then the controls points 80b77813a5dbce67e7ecb457e39900b82f04def8beztenghui // will be all coming from pathData. 81b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (TypedArrayUtils.hasAttribute(parser, "pathData")) { 82b77813a5dbce67e7ecb457e39900b82f04def8beztenghui String pathData = TypedArrayUtils.getNamedString(a, parser, "pathData", 83b77813a5dbce67e7ecb457e39900b82f04def8beztenghui AndroidResources.STYLEABLE_PATH_INTERPOLATOR_PATH_DATA); 84b77813a5dbce67e7ecb457e39900b82f04def8beztenghui Path path = PathParser.createPathFromPathData(pathData); 85b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (path == null) { 86b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new InflateException("The path is null, which is created" 87b77813a5dbce67e7ecb457e39900b82f04def8beztenghui + " from " + pathData); 88b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 89b77813a5dbce67e7ecb457e39900b82f04def8beztenghui initPath(path); 90b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } else { 91b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (!TypedArrayUtils.hasAttribute(parser, "controlX1")) { 92b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new InflateException("pathInterpolator requires the controlX1 attribute"); 93b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } else if (!TypedArrayUtils.hasAttribute(parser, "controlY1")) { 94b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new InflateException("pathInterpolator requires the controlY1 attribute"); 95b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 96b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float x1 = TypedArrayUtils.getNamedFloat(a, parser, "controlX1", 97b77813a5dbce67e7ecb457e39900b82f04def8beztenghui AndroidResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_1, 0); 98b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float y1 = TypedArrayUtils.getNamedFloat(a, parser, "controlY1", 99b77813a5dbce67e7ecb457e39900b82f04def8beztenghui AndroidResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_1, 0); 100b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 101b77813a5dbce67e7ecb457e39900b82f04def8beztenghui boolean hasX2 = TypedArrayUtils.hasAttribute(parser, "controlX2"); 102b77813a5dbce67e7ecb457e39900b82f04def8beztenghui boolean hasY2 = TypedArrayUtils.hasAttribute(parser, "controlY2"); 103b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 104b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (hasX2 != hasY2) { 105b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new InflateException("pathInterpolator requires both controlX2 and" 106b77813a5dbce67e7ecb457e39900b82f04def8beztenghui + " controlY2 for cubic Beziers."); 107b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 108b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 109b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (!hasX2) { 110b77813a5dbce67e7ecb457e39900b82f04def8beztenghui initQuad(x1, y1); 111b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } else { 112b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float x2 = TypedArrayUtils.getNamedFloat(a, parser, "controlX2", 113b77813a5dbce67e7ecb457e39900b82f04def8beztenghui AndroidResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_2, 0); 114b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float y2 = TypedArrayUtils.getNamedFloat(a, parser, "controlY2", 115b77813a5dbce67e7ecb457e39900b82f04def8beztenghui AndroidResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_2, 0); 116b77813a5dbce67e7ecb457e39900b82f04def8beztenghui initCubic(x1, y1, x2, y2); 117b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 118b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 119b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 120b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 121b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private void initQuad(float controlX, float controlY) { 122b77813a5dbce67e7ecb457e39900b82f04def8beztenghui Path path = new Path(); 123b77813a5dbce67e7ecb457e39900b82f04def8beztenghui path.moveTo(0, 0); 124b77813a5dbce67e7ecb457e39900b82f04def8beztenghui path.quadTo(controlX, controlY, 1f, 1f); 125b77813a5dbce67e7ecb457e39900b82f04def8beztenghui initPath(path); 126b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 127b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 128b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private void initCubic(float x1, float y1, float x2, float y2) { 129b77813a5dbce67e7ecb457e39900b82f04def8beztenghui Path path = new Path(); 130b77813a5dbce67e7ecb457e39900b82f04def8beztenghui path.moveTo(0, 0); 131b77813a5dbce67e7ecb457e39900b82f04def8beztenghui path.cubicTo(x1, y1, x2, y2, 1f, 1f); 132b77813a5dbce67e7ecb457e39900b82f04def8beztenghui initPath(path); 133b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 134b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 135b77813a5dbce67e7ecb457e39900b82f04def8beztenghui private void initPath(Path path) { 136b77813a5dbce67e7ecb457e39900b82f04def8beztenghui final PathMeasure pathMeasure = new PathMeasure(path, false /* forceClosed */); 137b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 138b77813a5dbce67e7ecb457e39900b82f04def8beztenghui final float pathLength = pathMeasure.getLength(); 139b77813a5dbce67e7ecb457e39900b82f04def8beztenghui final int numPoints = min(MAX_NUM_POINTS, (int) (pathLength / PRECISION) + 1); 140b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 141b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (numPoints <= 0) { 142b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new IllegalArgumentException("The Path has a invalid length " + pathLength); 143b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 144b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 145b77813a5dbce67e7ecb457e39900b82f04def8beztenghui mX = new float[numPoints]; 146b77813a5dbce67e7ecb457e39900b82f04def8beztenghui mY = new float[numPoints]; 147b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 148b77813a5dbce67e7ecb457e39900b82f04def8beztenghui final float[] position = new float[2]; 149b77813a5dbce67e7ecb457e39900b82f04def8beztenghui for (int i = 0; i < numPoints; ++i) { 150b77813a5dbce67e7ecb457e39900b82f04def8beztenghui final float distance = (i * pathLength) / (numPoints - 1); 151b77813a5dbce67e7ecb457e39900b82f04def8beztenghui pathMeasure.getPosTan(distance, position, null /* tangent */); 152b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 153b77813a5dbce67e7ecb457e39900b82f04def8beztenghui mX[i] = position[0]; 154b77813a5dbce67e7ecb457e39900b82f04def8beztenghui mY[i] = position[1]; 155b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 156b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 157b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (abs(mX[0]) > EPSILON || abs(mY[0]) > EPSILON || abs(mX[numPoints - 1] - 1) > EPSILON 158b77813a5dbce67e7ecb457e39900b82f04def8beztenghui || abs(mY[numPoints - 1] - 1) > EPSILON) { 159b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)" 160b77813a5dbce67e7ecb457e39900b82f04def8beztenghui + " start: " + mX[0] + "," + mY[0] + " end:" + mX[numPoints - 1] + "," 161b77813a5dbce67e7ecb457e39900b82f04def8beztenghui + mY[numPoints - 1]); 162b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 163b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 164b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 165b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float prevX = 0; 166b77813a5dbce67e7ecb457e39900b82f04def8beztenghui int componentIndex = 0; 167b77813a5dbce67e7ecb457e39900b82f04def8beztenghui for (int i = 0; i < numPoints; i++) { 168b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float x = mX[componentIndex++]; 169b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (x < prevX) { 170b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new IllegalArgumentException("The Path cannot loop back on itself, x :" + x); 171b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 172b77813a5dbce67e7ecb457e39900b82f04def8beztenghui mX[i] = x; 173b77813a5dbce67e7ecb457e39900b82f04def8beztenghui prevX = x; 174b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 175b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 176b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (pathMeasure.nextContour()) { 177b77813a5dbce67e7ecb457e39900b82f04def8beztenghui throw new IllegalArgumentException("The Path should be continuous," 178b77813a5dbce67e7ecb457e39900b82f04def8beztenghui + " can't have 2+ contours"); 179b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 180b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 181b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 182b77813a5dbce67e7ecb457e39900b82f04def8beztenghui /** 183b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * Using the line in the Path in this interpolator that can be described as 184b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code> 185b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * as the x coordinate. Values less than 0 will always return 0 and values greater 186b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * than 1 will always return 1. 187b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * 188b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * @param t Treated as the x coordinate along the line. 189b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * @return The y coordinate of the Path along the line where x = <code>t</code>. 190b77813a5dbce67e7ecb457e39900b82f04def8beztenghui * @see Interpolator#getInterpolation(float) 191b77813a5dbce67e7ecb457e39900b82f04def8beztenghui */ 192b77813a5dbce67e7ecb457e39900b82f04def8beztenghui @Override 193b77813a5dbce67e7ecb457e39900b82f04def8beztenghui public float getInterpolation(float t) { 194b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (t <= 0) { 195b77813a5dbce67e7ecb457e39900b82f04def8beztenghui return 0; 196b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } else if (t >= 1) { 197b77813a5dbce67e7ecb457e39900b82f04def8beztenghui return 1; 198b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 199b77813a5dbce67e7ecb457e39900b82f04def8beztenghui // Do a binary search for the correct x to interpolate between. 200b77813a5dbce67e7ecb457e39900b82f04def8beztenghui int startIndex = 0; 201b77813a5dbce67e7ecb457e39900b82f04def8beztenghui int endIndex = mX.length - 1; 202b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 203b77813a5dbce67e7ecb457e39900b82f04def8beztenghui while (endIndex - startIndex > 1) { 204b77813a5dbce67e7ecb457e39900b82f04def8beztenghui int midIndex = (startIndex + endIndex) / 2; 205b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (t < mX[midIndex]) { 206b77813a5dbce67e7ecb457e39900b82f04def8beztenghui endIndex = midIndex; 207b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } else { 208b77813a5dbce67e7ecb457e39900b82f04def8beztenghui startIndex = midIndex; 209b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 210b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 211b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 212b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float xRange = mX[endIndex] - mX[startIndex]; 213b77813a5dbce67e7ecb457e39900b82f04def8beztenghui if (xRange == 0) { 214b77813a5dbce67e7ecb457e39900b82f04def8beztenghui return mY[startIndex]; 215b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 216b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 217b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float tInRange = t - mX[startIndex]; 218b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float fraction = tInRange / xRange; 219b77813a5dbce67e7ecb457e39900b82f04def8beztenghui 220b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float startY = mY[startIndex]; 221b77813a5dbce67e7ecb457e39900b82f04def8beztenghui float endY = mY[endIndex]; 222b77813a5dbce67e7ecb457e39900b82f04def8beztenghui return startY + (fraction * (endY - startY)); 223b77813a5dbce67e7ecb457e39900b82f04def8beztenghui } 224b77813a5dbce67e7ecb457e39900b82f04def8beztenghui} 225