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 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) { 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 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 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 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