• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
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   */
16  package android.animation;
17  
18  import android.graphics.Path;
19  import android.graphics.PointF;
20  
21  import 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>
37   * @hide
38   */
39  public class PathKeyframes implements Keyframes {
40      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  
PathKeyframes(Path path)49      public PathKeyframes(Path path) {
50          this(path, 0.5f);
51      }
52  
PathKeyframes(Path path, float error)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
getKeyframes()61      public ArrayList<Keyframe> getKeyframes() {
62          return EMPTY_KEYFRAMES;
63      }
64  
65      @Override
getValue(float fraction)66      public Object getValue(float fraction) {
67          int numPoints = mKeyframeData.length / 3;
68          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) {
73              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
95              return interpolateInRange(fraction, high, low);
96          }
97      }
98  
interpolateInRange(float fraction, int startIndex, int endIndex)99      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  
120      @Override
invalidateCache()121      public void invalidateCache() {
122      }
123  
124      @Override
setEvaluator(TypeEvaluator evaluator)125      public void setEvaluator(TypeEvaluator evaluator) {
126      }
127  
128      @Override
getType()129      public Class getType() {
130          return PointF.class;
131      }
132  
133      @Override
clone()134      public Keyframes clone() {
135          Keyframes clone = null;
136          try {
137              clone = (Keyframes) super.clone();
138          } catch (CloneNotSupportedException e) {}
139          return clone;
140      }
141  
pointForIndex(int index)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  
interpolate(float fraction, float startValue, float endValue)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       */
createXFloatKeyframes()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  
235      abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
236          @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  
247      abstract static class FloatKeyframesBase extends SimpleKeyframes
248              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  }
260