blob: 50a490e955d6b4c1345d91075b79170ce9cb13e0 [file] [log] [blame]
George Mount984011f2014-08-21 14:28:01 -07001/*
2 * Copyright (C) 2014 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 */
16package android.animation;
17
18import android.graphics.Path;
19import android.graphics.PointF;
George Mount984011f2014-08-21 14:28:01 -070020
21import java.util.ArrayList;
22
23/**
24 * PathKeyframes relies on approximating the Path as a series of line segments.
25 * The line segments are recursively divided until there is less than 1/2 pixel error
26 * between the lines and the curve. Each point of the line segment is converted
27 * to a Keyframe and a linear interpolation between Keyframes creates a good approximation
28 * of the curve.
29 * <p>
30 * PathKeyframes is optimized to reduce the number of objects created when there are
31 * many keyframes for a curve.
32 * </p>
33 * <p>
34 * Typically, the returned type is a PointF, but the individual components can be extracted
35 * as either an IntKeyframes or FloatKeyframes.
36 * </p>
Jorim Jaggi787e9dd2016-03-15 10:52:40 +010037 * @hide
George Mount984011f2014-08-21 14:28:01 -070038 */
Jorim Jaggi787e9dd2016-03-15 10:52:40 +010039public class PathKeyframes implements Keyframes {
George Mount984011f2014-08-21 14:28:01 -070040 private static final int FRACTION_OFFSET = 0;
41 private static final int X_OFFSET = 1;
42 private static final int Y_OFFSET = 2;
43 private static final int NUM_COMPONENTS = 3;
44 private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>();
45
46 private PointF mTempPointF = new PointF();
47 private float[] mKeyframeData;
48
49 public PathKeyframes(Path path) {
50 this(path, 0.5f);
51 }
52
53 public PathKeyframes(Path path, float error) {
54 if (path == null || path.isEmpty()) {
55 throw new IllegalArgumentException("The path must not be null or empty");
56 }
57 mKeyframeData = path.approximate(error);
58 }
59
60 @Override
61 public ArrayList<Keyframe> getKeyframes() {
62 return EMPTY_KEYFRAMES;
63 }
64
65 @Override
66 public Object getValue(float fraction) {
George Mount984011f2014-08-21 14:28:01 -070067 int numPoints = mKeyframeData.length / 3;
George Mountbc68f5a2014-09-12 13:21:32 -070068 if (fraction < 0) {
69 return interpolateInRange(fraction, 0, 1);
70 } else if (fraction > 1) {
71 return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
72 } else if (fraction == 0) {
George Mount984011f2014-08-21 14:28:01 -070073 return pointForIndex(0);
74 } else if (fraction == 1) {
75 return pointForIndex(numPoints - 1);
76 } else {
77 // Binary search for the correct section
78 int low = 0;
79 int high = numPoints - 1;
80
81 while (low <= high) {
82 int mid = (low + high) / 2;
83 float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
84
85 if (fraction < midFraction) {
86 high = mid - 1;
87 } else if (fraction > midFraction) {
88 low = mid + 1;
89 } else {
90 return pointForIndex(mid);
91 }
92 }
93
94 // now high is below the fraction and low is above the fraction
George Mountbc68f5a2014-09-12 13:21:32 -070095 return interpolateInRange(fraction, high, low);
George Mount984011f2014-08-21 14:28:01 -070096 }
97 }
98
George Mountbc68f5a2014-09-12 13:21:32 -070099 private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
100 int startBase = (startIndex * NUM_COMPONENTS);
101 int endBase = (endIndex * NUM_COMPONENTS);
102
103 float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
104 float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
105
106 float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
107
108 float startX = mKeyframeData[startBase + X_OFFSET];
109 float endX = mKeyframeData[endBase + X_OFFSET];
110 float startY = mKeyframeData[startBase + Y_OFFSET];
111 float endY = mKeyframeData[endBase + Y_OFFSET];
112
113 float x = interpolate(intervalFraction, startX, endX);
114 float y = interpolate(intervalFraction, startY, endY);
115
116 mTempPointF.set(x, y);
117 return mTempPointF;
118 }
119
George Mount984011f2014-08-21 14:28:01 -0700120 @Override
121 public void invalidateCache() {
122 }
123
124 @Override
125 public void setEvaluator(TypeEvaluator evaluator) {
126 }
127
128 @Override
129 public Class getType() {
130 return PointF.class;
131 }
132
133 @Override
134 public Keyframes clone() {
135 Keyframes clone = null;
136 try {
137 clone = (Keyframes) super.clone();
138 } catch (CloneNotSupportedException e) {}
139 return clone;
140 }
141
142 private PointF pointForIndex(int index) {
143 int base = (index * NUM_COMPONENTS);
144 int xOffset = base + X_OFFSET;
145 int yOffset = base + Y_OFFSET;
146 mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
147 return mTempPointF;
148 }
149
150 private static float interpolate(float fraction, float startValue, float endValue) {
151 float diff = endValue - startValue;
152 return startValue + (diff * fraction);
153 }
154
155 /**
156 * Returns a FloatKeyframes for the X component of the Path.
157 * @return a FloatKeyframes for the X component of the Path.
158 */
159 public FloatKeyframes createXFloatKeyframes() {
160 return new FloatKeyframesBase() {
161 @Override
162 public float getFloatValue(float fraction) {
163 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
164 return pointF.x;
165 }
166 };
167 }
168
169 /**
170 * Returns a FloatKeyframes for the Y component of the Path.
171 * @return a FloatKeyframes for the Y component of the Path.
172 */
173 public FloatKeyframes createYFloatKeyframes() {
174 return new FloatKeyframesBase() {
175 @Override
176 public float getFloatValue(float fraction) {
177 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
178 return pointF.y;
179 }
180 };
181 }
182
183 /**
184 * Returns an IntKeyframes for the X component of the Path.
185 * @return an IntKeyframes for the X component of the Path.
186 */
187 public IntKeyframes createXIntKeyframes() {
188 return new IntKeyframesBase() {
189 @Override
190 public int getIntValue(float fraction) {
191 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
192 return Math.round(pointF.x);
193 }
194 };
195 }
196
197 /**
198 * Returns an IntKeyframeSet for the Y component of the Path.
199 * @return an IntKeyframeSet for the Y component of the Path.
200 */
201 public IntKeyframes createYIntKeyframes() {
202 return new IntKeyframesBase() {
203 @Override
204 public int getIntValue(float fraction) {
205 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
206 return Math.round(pointF.y);
207 }
208 };
209 }
210
211 private abstract static class SimpleKeyframes implements Keyframes {
212 @Override
213 public void setEvaluator(TypeEvaluator evaluator) {
214 }
215
216 @Override
217 public void invalidateCache() {
218 }
219
220 @Override
221 public ArrayList<Keyframe> getKeyframes() {
222 return EMPTY_KEYFRAMES;
223 }
224
225 @Override
226 public Keyframes clone() {
227 Keyframes clone = null;
228 try {
229 clone = (Keyframes) super.clone();
230 } catch (CloneNotSupportedException e) {}
231 return clone;
232 }
233 }
234
Doris Liu766431a2016-02-04 22:17:11 +0000235 abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
George Mount984011f2014-08-21 14:28:01 -0700236 @Override
237 public Class getType() {
238 return Integer.class;
239 }
240
241 @Override
242 public Object getValue(float fraction) {
243 return getIntValue(fraction);
244 }
245 }
246
Doris Liu766431a2016-02-04 22:17:11 +0000247 abstract static class FloatKeyframesBase extends SimpleKeyframes
George Mount984011f2014-08-21 14:28:01 -0700248 implements FloatKeyframes {
249 @Override
250 public Class getType() {
251 return Float.class;
252 }
253
254 @Override
255 public Object getValue(float fraction) {
256 return getFloatValue(fraction);
257 }
258 }
259}