1/*
2 * Copyright (C) 2015 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
17package android.support.v4.view.animation;
18
19import android.graphics.Path;
20import android.graphics.PathMeasure;
21import android.view.animation.Interpolator;
22
23/**
24 * A path interpolator implementation compatible with API 4+.
25 */
26class PathInterpolatorDonut implements Interpolator {
27
28    /**
29     * Governs the accuracy of the approximation of the {@link Path}.
30     */
31    private static final float PRECISION = 0.002f;
32
33    private final float[] mX;
34    private final float[] mY;
35
36    public PathInterpolatorDonut(Path path) {
37        final PathMeasure pathMeasure = new PathMeasure(path, false /* forceClosed */);
38
39        final float pathLength = pathMeasure.getLength();
40        final int numPoints = (int) (pathLength / PRECISION) + 1;
41
42        mX = new float[numPoints];
43        mY = new float[numPoints];
44
45        final float[] position = new float[2];
46        for (int i = 0; i < numPoints; ++i) {
47            final float distance = (i * pathLength) / (numPoints - 1);
48            pathMeasure.getPosTan(distance, position, null /* tangent */);
49
50            mX[i] = position[0];
51            mY[i] = position[1];
52        }
53    }
54
55    public PathInterpolatorDonut(float controlX, float controlY) {
56        this(createQuad(controlX, controlY));
57    }
58
59    public PathInterpolatorDonut(float controlX1, float controlY1,
60            float controlX2, float controlY2) {
61        this(createCubic(controlX1, controlY1, controlX2, controlY2));
62    }
63
64    @Override
65    public float getInterpolation(float t) {
66        if (t <= 0.0f) {
67            return 0.0f;
68        } else if (t >= 1.0f) {
69            return 1.0f;
70        }
71
72        // Do a binary search for the correct x to interpolate between.
73        int startIndex = 0;
74        int endIndex = mX.length - 1;
75        while (endIndex - startIndex > 1) {
76            int midIndex = (startIndex + endIndex) / 2;
77            if (t < mX[midIndex]) {
78                endIndex = midIndex;
79            } else {
80                startIndex = midIndex;
81            }
82        }
83
84        final float xRange = mX[endIndex] - mX[startIndex];
85        if (xRange == 0) {
86            return mY[startIndex];
87        }
88
89        final float tInRange = t - mX[startIndex];
90        final float fraction = tInRange / xRange;
91
92        final float startY = mY[startIndex];
93        final float endY = mY[endIndex];
94
95        return startY + (fraction * (endY - startY));
96    }
97
98    private static Path createQuad(float controlX, float controlY) {
99        final Path path = new Path();
100        path.moveTo(0.0f, 0.0f);
101        path.quadTo(controlX, controlY, 1.0f, 1.0f);
102        return path;
103    }
104
105    private static Path createCubic(float controlX1, float controlY1,
106            float controlX2, float controlY2) {
107        final Path path = new Path();
108        path.moveTo(0.0f, 0.0f);
109        path.cubicTo(controlX1, controlY1, controlX2, controlY2, 1.0f, 1.0f);
110        return path;
111    }
112}
113