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 setEvaluator(TypeEvaluator evaluator)121 public void setEvaluator(TypeEvaluator evaluator) { 122 } 123 124 @Override getType()125 public Class getType() { 126 return PointF.class; 127 } 128 129 @Override clone()130 public Keyframes clone() { 131 Keyframes clone = null; 132 try { 133 clone = (Keyframes) super.clone(); 134 } catch (CloneNotSupportedException e) {} 135 return clone; 136 } 137 pointForIndex(int index)138 private PointF pointForIndex(int index) { 139 int base = (index * NUM_COMPONENTS); 140 int xOffset = base + X_OFFSET; 141 int yOffset = base + Y_OFFSET; 142 mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]); 143 return mTempPointF; 144 } 145 interpolate(float fraction, float startValue, float endValue)146 private static float interpolate(float fraction, float startValue, float endValue) { 147 float diff = endValue - startValue; 148 return startValue + (diff * fraction); 149 } 150 151 /** 152 * Returns a FloatKeyframes for the X component of the Path. 153 * @return a FloatKeyframes for the X component of the Path. 154 */ createXFloatKeyframes()155 public FloatKeyframes createXFloatKeyframes() { 156 return new FloatKeyframesBase() { 157 @Override 158 public float getFloatValue(float fraction) { 159 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 160 return pointF.x; 161 } 162 }; 163 } 164 165 /** 166 * Returns a FloatKeyframes for the Y component of the Path. 167 * @return a FloatKeyframes for the Y component of the Path. 168 */ 169 public FloatKeyframes createYFloatKeyframes() { 170 return new FloatKeyframesBase() { 171 @Override 172 public float getFloatValue(float fraction) { 173 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 174 return pointF.y; 175 } 176 }; 177 } 178 179 /** 180 * Returns an IntKeyframes for the X component of the Path. 181 * @return an IntKeyframes for the X component of the Path. 182 */ 183 public IntKeyframes createXIntKeyframes() { 184 return new IntKeyframesBase() { 185 @Override 186 public int getIntValue(float fraction) { 187 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 188 return Math.round(pointF.x); 189 } 190 }; 191 } 192 193 /** 194 * Returns an IntKeyframeSet for the Y component of the Path. 195 * @return an IntKeyframeSet for the Y component of the Path. 196 */ 197 public IntKeyframes createYIntKeyframes() { 198 return new IntKeyframesBase() { 199 @Override 200 public int getIntValue(float fraction) { 201 PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); 202 return Math.round(pointF.y); 203 } 204 }; 205 } 206 207 private abstract static class SimpleKeyframes implements Keyframes { 208 @Override 209 public void setEvaluator(TypeEvaluator evaluator) { 210 } 211 212 @Override 213 public ArrayList<Keyframe> getKeyframes() { 214 return EMPTY_KEYFRAMES; 215 } 216 217 @Override 218 public Keyframes clone() { 219 Keyframes clone = null; 220 try { 221 clone = (Keyframes) super.clone(); 222 } catch (CloneNotSupportedException e) {} 223 return clone; 224 } 225 } 226 227 abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { 228 @Override 229 public Class getType() { 230 return Integer.class; 231 } 232 233 @Override 234 public Object getValue(float fraction) { 235 return getIntValue(fraction); 236 } 237 } 238 239 abstract static class FloatKeyframesBase extends SimpleKeyframes 240 implements FloatKeyframes { 241 @Override 242 public Class getType() { 243 return Float.class; 244 } 245 246 @Override 247 public Object getValue(float fraction) { 248 return getFloatValue(fraction); 249 } 250 } 251 } 252