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 */ 38 class PathKeyframes implements Keyframes { 39 private static final int FRACTION_OFFSET = 0; 40 private static final int X_OFFSET = 1; 41 private static final int Y_OFFSET = 2; 42 private static final int NUM_COMPONENTS = 3; 43 private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>(); 44 45 private PointF mTempPointF = new PointF(); 46 private float[] mKeyframeData; 47 PathKeyframes(Path path)48 public PathKeyframes(Path path) { 49 this(path, 0.5f); 50 } 51 PathKeyframes(Path path, float error)52 public PathKeyframes(Path path, float error) { 53 if (path == null || path.isEmpty()) { 54 throw new IllegalArgumentException("The path must not be null or empty"); 55 } 56 mKeyframeData = path.approximate(error); 57 } 58 59 @Override getKeyframes()60 public ArrayList<Keyframe> getKeyframes() { 61 return EMPTY_KEYFRAMES; 62 } 63 64 @Override getValue(float fraction)65 public Object getValue(float fraction) { 66 int numPoints = mKeyframeData.length / 3; 67 if (fraction < 0) { 68 return interpolateInRange(fraction, 0, 1); 69 } else if (fraction > 1) { 70 return interpolateInRange(fraction, numPoints - 2, numPoints - 1); 71 } else if (fraction == 0) { 72 return pointForIndex(0); 73 } else if (fraction == 1) { 74 return pointForIndex(numPoints - 1); 75 } else { 76 // Binary search for the correct section 77 int low = 0; 78 int high = numPoints - 1; 79 80 while (low <= high) { 81 int mid = (low + high) / 2; 82 float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET]; 83 84 if (fraction < midFraction) { 85 high = mid - 1; 86 } else if (fraction > midFraction) { 87 low = mid + 1; 88 } else { 89 return pointForIndex(mid); 90 } 91 } 92 93 // now high is below the fraction and low is above the fraction 94 return interpolateInRange(fraction, high, low); 95 } 96 } 97 interpolateInRange(float fraction, int startIndex, int endIndex)98 private PointF interpolateInRange(float fraction, int startIndex, int endIndex) { 99 int startBase = (startIndex * NUM_COMPONENTS); 100 int endBase = (endIndex * NUM_COMPONENTS); 101 102 float startFraction = mKeyframeData[startBase + FRACTION_OFFSET]; 103 float endFraction = mKeyframeData[endBase + FRACTION_OFFSET]; 104 105 float intervalFraction = (fraction - startFraction)/(endFraction - startFraction); 106 107 float startX = mKeyframeData[startBase + X_OFFSET]; 108 float endX = mKeyframeData[endBase + X_OFFSET]; 109 float startY = mKeyframeData[startBase + Y_OFFSET]; 110 float endY = mKeyframeData[endBase + Y_OFFSET]; 111 112 float x = interpolate(intervalFraction, startX, endX); 113 float y = interpolate(intervalFraction, startY, endY); 114 115 mTempPointF.set(x, y); 116 return mTempPointF; 117 } 118 119 @Override invalidateCache()120 public void invalidateCache() { 121 } 122 123 @Override setEvaluator(TypeEvaluator evaluator)124 public void setEvaluator(TypeEvaluator evaluator) { 125 } 126 127 @Override getType()128 public Class getType() { 129 return PointF.class; 130 } 131 132 @Override clone()133 public Keyframes clone() { 134 Keyframes clone = null; 135 try { 136 clone = (Keyframes) super.clone(); 137 } catch (CloneNotSupportedException e) {} 138 return clone; 139 } 140 pointForIndex(int index)141 private PointF pointForIndex(int index) { 142 int base = (index * NUM_COMPONENTS); 143 int xOffset = base + X_OFFSET; 144 int yOffset = base + Y_OFFSET; 145 mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]); 146 return mTempPointF; 147 } 148 interpolate(float fraction, float startValue, float endValue)149 private static float interpolate(float fraction, float startValue, float endValue) { 150 float diff = endValue - startValue; 151 return startValue + (diff * fraction); 152 } 153 154 /** 155 * Returns a FloatKeyframes for the X component of the Path. 156 * @return a FloatKeyframes for the X component of the Path. 157 */ createXFloatKeyframes()158 public FloatKeyframes createXFloatKeyframes() { 159 return new FloatKeyframesBase() { 160 @Override 161 public float getFloatValue(float fraction) { 162 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 163 return pointF.x; 164 } 165 }; 166 } 167 168 /** 169 * Returns a FloatKeyframes for the Y component of the Path. 170 * @return a FloatKeyframes for the Y component of the Path. 171 */ 172 public FloatKeyframes createYFloatKeyframes() { 173 return new FloatKeyframesBase() { 174 @Override 175 public float getFloatValue(float fraction) { 176 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 177 return pointF.y; 178 } 179 }; 180 } 181 182 /** 183 * Returns an IntKeyframes for the X component of the Path. 184 * @return an IntKeyframes for the X component of the Path. 185 */ 186 public IntKeyframes createXIntKeyframes() { 187 return new IntKeyframesBase() { 188 @Override 189 public int getIntValue(float fraction) { 190 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 191 return Math.round(pointF.x); 192 } 193 }; 194 } 195 196 /** 197 * Returns an IntKeyframeSet for the Y component of the Path. 198 * @return an IntKeyframeSet for the Y component of the Path. 199 */ 200 public IntKeyframes createYIntKeyframes() { 201 return new IntKeyframesBase() { 202 @Override 203 public int getIntValue(float fraction) { 204 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 205 return Math.round(pointF.y); 206 } 207 }; 208 } 209 210 private abstract static class SimpleKeyframes implements Keyframes { 211 @Override 212 public void setEvaluator(TypeEvaluator evaluator) { 213 } 214 215 @Override 216 public void invalidateCache() { 217 } 218 219 @Override 220 public ArrayList<Keyframe> getKeyframes() { 221 return EMPTY_KEYFRAMES; 222 } 223 224 @Override 225 public Keyframes clone() { 226 Keyframes clone = null; 227 try { 228 clone = (Keyframes) super.clone(); 229 } catch (CloneNotSupportedException e) {} 230 return clone; 231 } 232 } 233 234 private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { 235 @Override 236 public Class getType() { 237 return Integer.class; 238 } 239 240 @Override 241 public Object getValue(float fraction) { 242 return getIntValue(fraction); 243 } 244 } 245 246 private abstract static class FloatKeyframesBase extends SimpleKeyframes 247 implements FloatKeyframes { 248 @Override 249 public Class getType() { 250 return Float.class; 251 } 252 253 @Override 254 public Object getValue(float fraction) { 255 return getFloatValue(fraction); 256 } 257 } 258 } 259