blob: 8d03b551a3479f9baad7c70508cbdfabef163fd3 [file] [log] [blame]
Chet Haased51d3682010-08-11 19:46:48 -07001/*
2 * Copyright (C) 2010 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
Tor Norbye7b9c9122013-05-30 16:48:33 -070018import android.annotation.AnimatorRes;
Chet Haased51d3682010-08-11 19:46:48 -070019import android.content.Context;
Yigit Boyard422dc32014-09-25 12:23:35 -070020import android.content.res.ConfigurationBoundResourceCache;
21import android.content.res.ConstantState;
Chet Haased51d3682010-08-11 19:46:48 -070022import android.content.res.Resources;
ztenghuicf4832f2014-06-17 09:54:45 -070023import android.content.res.Resources.NotFoundException;
ztenghuie5e92602014-06-03 14:02:10 -070024import android.content.res.Resources.Theme;
Chet Haased51d3682010-08-11 19:46:48 -070025import android.content.res.TypedArray;
26import android.content.res.XmlResourceParser;
ztenghuicf4832f2014-06-17 09:54:45 -070027import android.graphics.Path;
Chet Haased51d3682010-08-11 19:46:48 -070028import android.util.AttributeSet;
ztenghuieb034fb2014-06-09 13:14:19 -070029import android.util.Log;
ztenghuicf4832f2014-06-17 09:54:45 -070030import android.util.PathParser;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070031import android.util.StateSet;
Chet Haase5bed88e12010-11-30 14:43:33 -080032import android.util.TypedValue;
Chet Haased51d3682010-08-11 19:46:48 -070033import android.util.Xml;
ztenghuieb034fb2014-06-09 13:14:19 -070034import android.view.InflateException;
Chet Haased51d3682010-08-11 19:46:48 -070035import android.view.animation.AnimationUtils;
Yigit Boyard422dc32014-09-25 12:23:35 -070036import android.view.animation.BaseInterpolator;
37import android.view.animation.Interpolator;
ztenghuie5e92602014-06-03 14:02:10 -070038
39import com.android.internal.R;
40
Chet Haased51d3682010-08-11 19:46:48 -070041import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43
44import java.io.IOException;
45import java.util.ArrayList;
46
47/**
Chet Haase6cfdf452011-04-22 16:42:10 -070048 * This class is used to instantiate animator XML files into Animator objects.
Chet Haased51d3682010-08-11 19:46:48 -070049 * <p>
Chet Haase6cfdf452011-04-22 16:42:10 -070050 * For performance reasons, inflation relies heavily on pre-processing of
Chet Haased51d3682010-08-11 19:46:48 -070051 * XML files that is done at build time. Therefore, it is not currently possible
Chet Haase6cfdf452011-04-22 16:42:10 -070052 * to use this inflater with an XmlPullParser over a plain XML file at runtime;
Chet Haased51d3682010-08-11 19:46:48 -070053 * it only works with an XmlPullParser returned from a compiled resource (R.
54 * <em>something</em> file.)
55 */
Chet Haasea18a86b2010-09-07 13:20:00 -070056public class AnimatorInflater {
ztenghuieb034fb2014-06-09 13:14:19 -070057 private static final String TAG = "AnimatorInflater";
Chet Haased51d3682010-08-11 19:46:48 -070058 /**
Chet Haasea18a86b2010-09-07 13:20:00 -070059 * These flags are used when parsing AnimatorSet objects
Chet Haased51d3682010-08-11 19:46:48 -070060 */
61 private static final int TOGETHER = 0;
62 private static final int SEQUENTIALLY = 1;
63
64 /**
65 * Enum values used in XML attributes to indicate the value for mValueType
66 */
67 private static final int VALUE_TYPE_FLOAT = 0;
68 private static final int VALUE_TYPE_INT = 1;
ztenghuieb034fb2014-06-09 13:14:19 -070069 private static final int VALUE_TYPE_PATH = 2;
Chet Haased4307532014-12-01 06:32:38 -080070 private static final int VALUE_TYPE_COLOR = 3;
Doris Liu9032fa52015-04-09 20:11:22 -070071 private static final int VALUE_TYPE_UNDEFINED = 4;
Chet Haased51d3682010-08-11 19:46:48 -070072
ztenghuieb034fb2014-06-09 13:14:19 -070073 private static final boolean DBG_ANIMATOR_INFLATER = false;
74
Yigit Boyard422dc32014-09-25 12:23:35 -070075 // used to calculate changing configs for resource references
76 private static final TypedValue sTmpTypedValue = new TypedValue();
77
Chet Haased51d3682010-08-11 19:46:48 -070078 /**
Chet Haasea18a86b2010-09-07 13:20:00 -070079 * Loads an {@link Animator} object from a resource
Chet Haased51d3682010-08-11 19:46:48 -070080 *
Doris Liu7513aab2015-04-18 00:14:14 +000081 * @param context Application context used to access resources
Chet Haased51d3682010-08-11 19:46:48 -070082 * @param id The resource id of the animation to load
Chet Haasea18a86b2010-09-07 13:20:00 -070083 * @return The animator object reference by the specified id
Chet Haased51d3682010-08-11 19:46:48 -070084 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
85 */
Tor Norbye7b9c9122013-05-30 16:48:33 -070086 public static Animator loadAnimator(Context context, @AnimatorRes int id)
Chet Haased51d3682010-08-11 19:46:48 -070087 throws NotFoundException {
Doris Liu7513aab2015-04-18 00:14:14 +000088 return loadAnimator(context.getResources(), context.getTheme(), id);
ztenghuie5e92602014-06-03 14:02:10 -070089 }
90
91 /**
92 * Loads an {@link Animator} object from a resource
93 *
Doris Liu7513aab2015-04-18 00:14:14 +000094 * @param resources The resources
ztenghuie5e92602014-06-03 14:02:10 -070095 * @param theme The theme
96 * @param id The resource id of the animation to load
97 * @return The animator object reference by the specified id
98 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
99 * @hide
100 */
Doris Liu7513aab2015-04-18 00:14:14 +0000101 public static Animator loadAnimator(Resources resources, Theme theme, int id)
ztenghuie5e92602014-06-03 14:02:10 -0700102 throws NotFoundException {
Doris Liu7513aab2015-04-18 00:14:14 +0000103 return loadAnimator(resources, theme, id, 1);
George Mountfd3c4742014-09-08 13:23:36 -0700104 }
105
106 /** @hide */
107 public static Animator loadAnimator(Resources resources, Theme theme, int id,
108 float pathErrorScale) throws NotFoundException {
Yigit Boyard422dc32014-09-25 12:23:35 -0700109 final ConfigurationBoundResourceCache<Animator> animatorCache = resources
110 .getAnimatorCache();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800111 Animator animator = animatorCache.getInstance(id, resources, theme);
Yigit Boyard422dc32014-09-25 12:23:35 -0700112 if (animator != null) {
113 if (DBG_ANIMATOR_INFLATER) {
114 Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
115 }
116 return animator;
117 } else if (DBG_ANIMATOR_INFLATER) {
118 Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
119 }
Chet Haased51d3682010-08-11 19:46:48 -0700120 XmlResourceParser parser = null;
121 try {
ztenghuie5e92602014-06-03 14:02:10 -0700122 parser = resources.getAnimation(id);
Doris Liu7513aab2015-04-18 00:14:14 +0000123 animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
Yigit Boyard422dc32014-09-25 12:23:35 -0700124 if (animator != null) {
125 animator.appendChangingConfigurations(getChangingConfigs(resources, id));
126 final ConstantState<Animator> constantState = animator.createConstantState();
127 if (constantState != null) {
128 if (DBG_ANIMATOR_INFLATER) {
129 Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
130 }
131 animatorCache.put(id, theme, constantState);
132 // create a new animator so that cached version is never used by the user
133 animator = constantState.newInstance(resources, theme);
134 }
135 }
136 return animator;
Chet Haased51d3682010-08-11 19:46:48 -0700137 } catch (XmlPullParserException ex) {
138 Resources.NotFoundException rnf =
139 new Resources.NotFoundException("Can't load animation resource ID #0x" +
George Mountfd3c4742014-09-08 13:23:36 -0700140 Integer.toHexString(id));
Chet Haased51d3682010-08-11 19:46:48 -0700141 rnf.initCause(ex);
142 throw rnf;
143 } catch (IOException ex) {
144 Resources.NotFoundException rnf =
145 new Resources.NotFoundException("Can't load animation resource ID #0x" +
George Mountfd3c4742014-09-08 13:23:36 -0700146 Integer.toHexString(id));
Chet Haased51d3682010-08-11 19:46:48 -0700147 rnf.initCause(ex);
148 throw rnf;
149 } finally {
150 if (parser != null) parser.close();
151 }
152 }
153
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700154 public static StateListAnimator loadStateListAnimator(Context context, int id)
155 throws NotFoundException {
Yigit Boyard422dc32014-09-25 12:23:35 -0700156 final Resources resources = context.getResources();
157 final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
158 .getStateListAnimatorCache();
159 final Theme theme = context.getTheme();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800160 StateListAnimator animator = cache.getInstance(id, resources, theme);
Yigit Boyard422dc32014-09-25 12:23:35 -0700161 if (animator != null) {
162 return animator;
163 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700164 XmlResourceParser parser = null;
165 try {
Yigit Boyard422dc32014-09-25 12:23:35 -0700166 parser = resources.getAnimation(id);
167 animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
168 if (animator != null) {
169 animator.appendChangingConfigurations(getChangingConfigs(resources, id));
170 final ConstantState<StateListAnimator> constantState = animator
171 .createConstantState();
172 if (constantState != null) {
173 cache.put(id, theme, constantState);
174 // return a clone so that the animator in constant state is never used.
175 animator = constantState.newInstance(resources, theme);
176 }
177 }
178 return animator;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700179 } catch (XmlPullParserException ex) {
180 Resources.NotFoundException rnf =
181 new Resources.NotFoundException(
182 "Can't load state list animator resource ID #0x" +
183 Integer.toHexString(id)
184 );
185 rnf.initCause(ex);
186 throw rnf;
187 } catch (IOException ex) {
188 Resources.NotFoundException rnf =
189 new Resources.NotFoundException(
190 "Can't load state list animator resource ID #0x" +
191 Integer.toHexString(id)
192 );
193 rnf.initCause(ex);
194 throw rnf;
195 } finally {
196 if (parser != null) {
197 parser.close();
198 }
199 }
200 }
201
202 private static StateListAnimator createStateListAnimatorFromXml(Context context,
203 XmlPullParser parser, AttributeSet attributeSet)
204 throws IOException, XmlPullParserException {
205 int type;
206 StateListAnimator stateListAnimator = new StateListAnimator();
207
208 while (true) {
209 type = parser.next();
210 switch (type) {
211 case XmlPullParser.END_DOCUMENT:
212 case XmlPullParser.END_TAG:
213 return stateListAnimator;
214
215 case XmlPullParser.START_TAG:
216 // parse item
217 Animator animator = null;
218 if ("item".equals(parser.getName())) {
219 int attributeCount = parser.getAttributeCount();
220 int[] states = new int[attributeCount];
221 int stateIndex = 0;
222 for (int i = 0; i < attributeCount; i++) {
223 int attrName = attributeSet.getAttributeNameResource(i);
ztenghuicf4832f2014-06-17 09:54:45 -0700224 if (attrName == R.attr.animation) {
Yigit Boyard422dc32014-09-25 12:23:35 -0700225 final int animId = attributeSet.getAttributeResourceValue(i, 0);
226 animator = loadAnimator(context, animId);
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700227 } else {
228 states[stateIndex++] =
229 attributeSet.getAttributeBooleanValue(i, false) ?
230 attrName : -attrName;
231 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700232 }
233 if (animator == null) {
ztenghuie5e92602014-06-03 14:02:10 -0700234 animator = createAnimatorFromXml(context.getResources(),
Doris Liu7513aab2015-04-18 00:14:14 +0000235 context.getTheme(), parser, 1f);
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700236 }
237
238 if (animator == null) {
239 throw new Resources.NotFoundException(
240 "animation state item must have a valid animation");
241 }
242 stateListAnimator
243 .addState(StateSet.trimStateSet(states, stateIndex), animator);
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700244 }
245 break;
246 }
247 }
248 }
249
ztenghuicf4832f2014-06-17 09:54:45 -0700250 /**
ztenghuieb034fb2014-06-09 13:14:19 -0700251 * PathDataEvaluator is used to interpolate between two paths which are
252 * represented in the same format but different control points' values.
Doris Liu804618d2015-11-16 22:48:34 -0800253 * The path is represented as verbs and points for each of the verbs.
ztenghuieb034fb2014-06-09 13:14:19 -0700254 */
Doris Liu804618d2015-11-16 22:48:34 -0800255 private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
256 private final PathParser.PathData mPathData = new PathParser.PathData();
ztenghuieb034fb2014-06-09 13:14:19 -0700257
258 @Override
Doris Liu804618d2015-11-16 22:48:34 -0800259 public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
260 PathParser.PathData endPathData) {
261 if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
ztenghuieb034fb2014-06-09 13:14:19 -0700262 throw new IllegalArgumentException("Can't interpolate between"
263 + " two incompatible pathData");
264 }
Doris Liu804618d2015-11-16 22:48:34 -0800265 return mPathData;
ztenghuieb034fb2014-06-09 13:14:19 -0700266 }
267 }
268
Chet Haased4307532014-12-01 06:32:38 -0800269 private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
270 int valueFromId, int valueToId, String propertyName) {
271
Chet Haased4307532014-12-01 06:32:38 -0800272 TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
273 boolean hasFrom = (tvFrom != null);
274 int fromType = hasFrom ? tvFrom.type : 0;
275 TypedValue tvTo = styledAttributes.peekValue(valueToId);
276 boolean hasTo = (tvTo != null);
277 int toType = hasTo ? tvTo.type : 0;
278
Doris Liu9032fa52015-04-09 20:11:22 -0700279 if (valueType == VALUE_TYPE_UNDEFINED) {
280 // Check whether it's color type. If not, fall back to default type (i.e. float type)
281 if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
282 valueType = VALUE_TYPE_COLOR;
283 } else {
284 valueType = VALUE_TYPE_FLOAT;
285 }
286 }
287
288 boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
289
Chet Haased4307532014-12-01 06:32:38 -0800290 PropertyValuesHolder returnValue = null;
291
292 if (valueType == VALUE_TYPE_PATH) {
293 String fromString = styledAttributes.getString(valueFromId);
294 String toString = styledAttributes.getString(valueToId);
Doris Liu804618d2015-11-16 22:48:34 -0800295 PathParser.PathData nodesFrom = fromString == null
296 ? null : new PathParser.PathData(fromString);
297 PathParser.PathData nodesTo = toString == null
298 ? null : new PathParser.PathData(toString);
Chet Haased4307532014-12-01 06:32:38 -0800299
300 if (nodesFrom != null || nodesTo != null) {
301 if (nodesFrom != null) {
Doris Liu804618d2015-11-16 22:48:34 -0800302 TypeEvaluator evaluator = new PathDataEvaluator();
Chet Haased4307532014-12-01 06:32:38 -0800303 if (nodesTo != null) {
304 if (!PathParser.canMorph(nodesFrom, nodesTo)) {
305 throw new InflateException(" Can't morph from " + fromString + " to " +
306 toString);
307 }
308 returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
309 nodesFrom, nodesTo);
310 } else {
311 returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
312 (Object) nodesFrom);
313 }
314 } else if (nodesTo != null) {
Doris Liu804618d2015-11-16 22:48:34 -0800315 TypeEvaluator evaluator = new PathDataEvaluator();
Chet Haased4307532014-12-01 06:32:38 -0800316 returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
317 (Object) nodesTo);
318 }
319 }
320 } else {
321 TypeEvaluator evaluator = null;
322 // Integer and float value types are handled here.
Doris Liu9032fa52015-04-09 20:11:22 -0700323 if (valueType == VALUE_TYPE_COLOR) {
Chet Haased4307532014-12-01 06:32:38 -0800324 // special case for colors: ignore valueType and get ints
Chet Haased4307532014-12-01 06:32:38 -0800325 evaluator = ArgbEvaluator.getInstance();
326 }
327 if (getFloats) {
328 float valueFrom;
329 float valueTo;
330 if (hasFrom) {
331 if (fromType == TypedValue.TYPE_DIMENSION) {
332 valueFrom = styledAttributes.getDimension(valueFromId, 0f);
333 } else {
334 valueFrom = styledAttributes.getFloat(valueFromId, 0f);
335 }
336 if (hasTo) {
337 if (toType == TypedValue.TYPE_DIMENSION) {
338 valueTo = styledAttributes.getDimension(valueToId, 0f);
339 } else {
340 valueTo = styledAttributes.getFloat(valueToId, 0f);
341 }
342 returnValue = PropertyValuesHolder.ofFloat(propertyName,
343 valueFrom, valueTo);
344 } else {
345 returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom);
346 }
347 } else {
348 if (toType == TypedValue.TYPE_DIMENSION) {
349 valueTo = styledAttributes.getDimension(valueToId, 0f);
350 } else {
351 valueTo = styledAttributes.getFloat(valueToId, 0f);
352 }
353 returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo);
354 }
355 } else {
356 int valueFrom;
357 int valueTo;
358 if (hasFrom) {
359 if (fromType == TypedValue.TYPE_DIMENSION) {
360 valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700361 } else if (isColorType(fromType)) {
Chet Haased4307532014-12-01 06:32:38 -0800362 valueFrom = styledAttributes.getColor(valueFromId, 0);
363 } else {
364 valueFrom = styledAttributes.getInt(valueFromId, 0);
365 }
366 if (hasTo) {
367 if (toType == TypedValue.TYPE_DIMENSION) {
368 valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700369 } else if (isColorType(toType)) {
Chet Haased4307532014-12-01 06:32:38 -0800370 valueTo = styledAttributes.getColor(valueToId, 0);
371 } else {
372 valueTo = styledAttributes.getInt(valueToId, 0);
373 }
374 returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo);
375 } else {
376 returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom);
377 }
378 } else {
379 if (hasTo) {
380 if (toType == TypedValue.TYPE_DIMENSION) {
381 valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700382 } else if (isColorType(toType)) {
Chet Haased4307532014-12-01 06:32:38 -0800383 valueTo = styledAttributes.getColor(valueToId, 0);
384 } else {
385 valueTo = styledAttributes.getInt(valueToId, 0);
386 }
387 returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo);
388 }
389 }
390 }
391 if (returnValue != null && evaluator != null) {
392 returnValue.setEvaluator(evaluator);
393 }
394 }
395
396 return returnValue;
397 }
398
ztenghuieb034fb2014-06-09 13:14:19 -0700399 /**
Craig Stout7f9988f2014-08-07 15:25:04 -0700400 * @param anim The animator, must not be null
ztenghuicf4832f2014-06-17 09:54:45 -0700401 * @param arrayAnimator Incoming typed array for Animator's attributes.
402 * @param arrayObjectAnimator Incoming typed array for Object Animator's
403 * attributes.
George Mountfd3c4742014-09-08 13:23:36 -0700404 * @param pixelSize The relative pixel size, used to calculate the
405 * maximum error for path animations.
ztenghuicf4832f2014-06-17 09:54:45 -0700406 */
407 private static void parseAnimatorFromTypeArray(ValueAnimator anim,
George Mountfd3c4742014-09-08 13:23:36 -0700408 TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
ztenghuicf4832f2014-06-17 09:54:45 -0700409 long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
Chet Haased51d3682010-08-11 19:46:48 -0700410
ztenghuicf4832f2014-06-17 09:54:45 -0700411 long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
Chet Haased51d3682010-08-11 19:46:48 -0700412
Doris Liu2787ceb2015-06-05 17:03:21 -0700413 int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_UNDEFINED);
Chet Haased51d3682010-08-11 19:46:48 -0700414
Doris Liu2787ceb2015-06-05 17:03:21 -0700415 if (valueType == VALUE_TYPE_UNDEFINED) {
416 valueType = inferValueTypeFromValues(arrayAnimator, R.styleable.Animator_valueFrom,
417 R.styleable.Animator_valueTo);
418 }
Chet Haased4307532014-12-01 06:32:38 -0800419 PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType,
420 R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, "");
421 if (pvh != null) {
422 anim.setValues(pvh);
Chet Haase5bed88e12010-11-30 14:43:33 -0800423 }
424
ztenghuieb034fb2014-06-09 13:14:19 -0700425 anim.setDuration(duration);
426 anim.setStartDelay(startDelay);
427
428 if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
429 anim.setRepeatCount(
430 arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
431 }
432 if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
433 anim.setRepeatMode(
434 arrayAnimator.getInt(R.styleable.Animator_repeatMode,
435 ValueAnimator.RESTART));
436 }
ztenghuieb034fb2014-06-09 13:14:19 -0700437
438 if (arrayObjectAnimator != null) {
Chet Haased4307532014-12-01 06:32:38 -0800439 setupObjectAnimator(anim, arrayObjectAnimator, valueType == VALUE_TYPE_FLOAT,
440 pixelSize);
ztenghuieb034fb2014-06-09 13:14:19 -0700441 }
442 }
443
444 /**
445 * Setup the Animator to achieve path morphing.
446 *
447 * @param anim The target Animator which will be updated.
448 * @param arrayAnimator TypedArray for the ValueAnimator.
449 * @return the PathDataEvaluator.
450 */
451 private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim,
452 TypedArray arrayAnimator) {
453 TypeEvaluator evaluator = null;
454 String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
455 String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
Doris Liu804618d2015-11-16 22:48:34 -0800456 PathParser.PathData pathDataFrom = fromString == null
457 ? null : new PathParser.PathData(fromString);
458 PathParser.PathData pathDataTo = toString == null
459 ? null : new PathParser.PathData(toString);
ztenghuieb034fb2014-06-09 13:14:19 -0700460
Doris Liu804618d2015-11-16 22:48:34 -0800461 if (pathDataFrom != null) {
462 if (pathDataTo != null) {
463 anim.setObjectValues(pathDataFrom, pathDataTo);
464 if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
ztenghuieb034fb2014-06-09 13:14:19 -0700465 throw new InflateException(arrayAnimator.getPositionDescription()
466 + " Can't morph from " + fromString + " to " + toString);
467 }
468 } else {
Doris Liu804618d2015-11-16 22:48:34 -0800469 anim.setObjectValues((Object)pathDataFrom);
ztenghuieb034fb2014-06-09 13:14:19 -0700470 }
Doris Liu804618d2015-11-16 22:48:34 -0800471 evaluator = new PathDataEvaluator();
472 } else if (pathDataTo != null) {
473 anim.setObjectValues((Object)pathDataTo);
474 evaluator = new PathDataEvaluator();
ztenghuieb034fb2014-06-09 13:14:19 -0700475 }
476
477 if (DBG_ANIMATOR_INFLATER && evaluator != null) {
478 Log.v(TAG, "create a new PathDataEvaluator here");
479 }
480
481 return evaluator;
482 }
483
484 /**
485 * Setup ObjectAnimator's property or values from pathData.
486 *
487 * @param anim The target Animator which will be updated.
488 * @param arrayObjectAnimator TypedArray for the ObjectAnimator.
489 * @param getFloats True if the value type is float.
George Mountfd3c4742014-09-08 13:23:36 -0700490 * @param pixelSize The relative pixel size, used to calculate the
491 * maximum error for path animations.
ztenghuieb034fb2014-06-09 13:14:19 -0700492 */
493 private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator,
George Mountfd3c4742014-09-08 13:23:36 -0700494 boolean getFloats, float pixelSize) {
ztenghuieb034fb2014-06-09 13:14:19 -0700495 ObjectAnimator oa = (ObjectAnimator) anim;
496 String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
497
Doris Liu2787ceb2015-06-05 17:03:21 -0700498 // Path can be involved in an ObjectAnimator in the following 3 ways:
499 // 1) Path morphing: the property to be animated is pathData, and valueFrom and valueTo
500 // are both of pathType. valueType = pathType needs to be explicitly defined.
501 // 2) A property in X or Y dimension can be animated along a path: the property needs to be
502 // defined in propertyXName or propertyYName attribute, the path will be defined in the
503 // pathData attribute. valueFrom and valueTo will not be necessary for this animation.
504 // 3) PathInterpolator can also define a path (in pathData) for its interpolation curve.
505 // Here we are dealing with case 2:
ztenghuieb034fb2014-06-09 13:14:19 -0700506 if (pathData != null) {
507 String propertyXName =
508 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
509 String propertyYName =
510 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
511
512 if (propertyXName == null && propertyYName == null) {
513 throw new InflateException(arrayObjectAnimator.getPositionDescription()
514 + " propertyXName or propertyYName is needed for PathData");
515 } else {
516 Path path = PathParser.createPathFromPathData(pathData);
George Mountfd3c4742014-09-08 13:23:36 -0700517 float error = 0.5f * pixelSize; // max half a pixel error
518 PathKeyframes keyframeSet = KeyframeSet.ofPath(path, error);
George Mount984011f2014-08-21 14:28:01 -0700519 Keyframes xKeyframes;
520 Keyframes yKeyframes;
521 if (getFloats) {
522 xKeyframes = keyframeSet.createXFloatKeyframes();
523 yKeyframes = keyframeSet.createYFloatKeyframes();
524 } else {
525 xKeyframes = keyframeSet.createXIntKeyframes();
526 yKeyframes = keyframeSet.createYIntKeyframes();
527 }
ztenghuieb034fb2014-06-09 13:14:19 -0700528 PropertyValuesHolder x = null;
529 PropertyValuesHolder y = null;
530 if (propertyXName != null) {
George Mount984011f2014-08-21 14:28:01 -0700531 x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes);
ztenghuieb034fb2014-06-09 13:14:19 -0700532 }
533 if (propertyYName != null) {
George Mount984011f2014-08-21 14:28:01 -0700534 y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes);
ztenghuieb034fb2014-06-09 13:14:19 -0700535 }
536 if (x == null) {
537 oa.setValues(y);
538 } else if (y == null) {
539 oa.setValues(x);
540 } else {
541 oa.setValues(x, y);
542 }
543 }
544 } else {
545 String propertyName =
546 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
547 oa.setPropertyName(propertyName);
548 }
549 }
550
551 /**
552 * Setup ValueAnimator's values.
553 * This will handle all of the integer, float and color types.
554 *
555 * @param anim The target Animator which will be updated.
556 * @param arrayAnimator TypedArray for the ValueAnimator.
557 * @param getFloats True if the value type is float.
558 * @param hasFrom True if "valueFrom" exists.
559 * @param fromType The type of "valueFrom".
560 * @param hasTo True if "valueTo" exists.
561 * @param toType The type of "valueTo".
562 */
563 private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator,
564 boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) {
565 int valueFromIndex = R.styleable.Animator_valueFrom;
566 int valueToIndex = R.styleable.Animator_valueTo;
Chet Haase5bed88e12010-11-30 14:43:33 -0800567 if (getFloats) {
568 float valueFrom;
569 float valueTo;
570 if (hasFrom) {
571 if (fromType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700572 valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
Chet Haase2794eb32010-10-12 16:29:28 -0700573 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700574 valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
Romain Guy83d6e822010-10-14 10:13:53 -0700575 }
Chet Haase5bed88e12010-11-30 14:43:33 -0800576 if (hasTo) {
577 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700578 valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
Chet Haase2794eb32010-10-12 16:29:28 -0700579 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700580 valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
Chet Haase2794eb32010-10-12 16:29:28 -0700581 }
Chet Haase5bed88e12010-11-30 14:43:33 -0800582 anim.setFloatValues(valueFrom, valueTo);
Chet Haase2794eb32010-10-12 16:29:28 -0700583 } else {
Chet Haase5bed88e12010-11-30 14:43:33 -0800584 anim.setFloatValues(valueFrom);
585 }
586 } else {
587 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700588 valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
Chet Haase5bed88e12010-11-30 14:43:33 -0800589 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700590 valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
Chet Haase5bed88e12010-11-30 14:43:33 -0800591 }
592 anim.setFloatValues(valueTo);
593 }
594 } else {
595 int valueFrom;
596 int valueTo;
597 if (hasFrom) {
598 if (fromType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700599 valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700600 } else if (isColorType(fromType)) {
ztenghuicf4832f2014-06-17 09:54:45 -0700601 valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800602 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700603 valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800604 }
605 if (hasTo) {
606 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700607 valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700608 } else if (isColorType(toType)) {
ztenghuicf4832f2014-06-17 09:54:45 -0700609 valueTo = arrayAnimator.getColor(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800610 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700611 valueTo = arrayAnimator.getInt(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800612 }
613 anim.setIntValues(valueFrom, valueTo);
614 } else {
615 anim.setIntValues(valueFrom);
616 }
617 } else {
618 if (hasTo) {
619 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700620 valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700621 } else if (isColorType(toType)) {
ztenghuicf4832f2014-06-17 09:54:45 -0700622 valueTo = arrayAnimator.getColor(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800623 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700624 valueTo = arrayAnimator.getInt(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800625 }
Chet Haase2794eb32010-10-12 16:29:28 -0700626 anim.setIntValues(valueTo);
Chet Haased51d3682010-08-11 19:46:48 -0700627 }
Chet Haase2794eb32010-10-12 16:29:28 -0700628 }
Chet Haased51d3682010-08-11 19:46:48 -0700629 }
ztenghuie5e92602014-06-03 14:02:10 -0700630 }
631
George Mountfd3c4742014-09-08 13:23:36 -0700632 private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
Doris Liu7513aab2015-04-18 00:14:14 +0000633 float pixelSize)
ztenghuie5e92602014-06-03 14:02:10 -0700634 throws XmlPullParserException, IOException {
George Mountfd3c4742014-09-08 13:23:36 -0700635 return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0,
Doris Liu7513aab2015-04-18 00:14:14 +0000636 pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700637 }
638
639 private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
Doris Liu7513aab2015-04-18 00:14:14 +0000640 AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
641 throws XmlPullParserException, IOException {
ztenghuie5e92602014-06-03 14:02:10 -0700642 Animator anim = null;
643 ArrayList<Animator> childAnims = null;
644
645 // Make sure we are on a start tag.
646 int type;
647 int depth = parser.getDepth();
648
649 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
650 && type != XmlPullParser.END_DOCUMENT) {
651
652 if (type != XmlPullParser.START_TAG) {
653 continue;
654 }
655
656 String name = parser.getName();
Chet Haased4307532014-12-01 06:32:38 -0800657 boolean gotValues = false;
ztenghuie5e92602014-06-03 14:02:10 -0700658
659 if (name.equals("objectAnimator")) {
Doris Liu7513aab2015-04-18 00:14:14 +0000660 anim = loadObjectAnimator(res, theme, attrs, pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700661 } else if (name.equals("animator")) {
Doris Liu7513aab2015-04-18 00:14:14 +0000662 anim = loadAnimator(res, theme, attrs, null, pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700663 } else if (name.equals("set")) {
664 anim = new AnimatorSet();
665 TypedArray a;
666 if (theme != null) {
ztenghuicf4832f2014-06-17 09:54:45 -0700667 a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
ztenghuie5e92602014-06-03 14:02:10 -0700668 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700669 a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
ztenghuie5e92602014-06-03 14:02:10 -0700670 }
Yigit Boyard422dc32014-09-25 12:23:35 -0700671 anim.appendChangingConfigurations(a.getChangingConfigurations());
672 int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
George Mountfd3c4742014-09-08 13:23:36 -0700673 createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
Doris Liu7513aab2015-04-18 00:14:14 +0000674 pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700675 a.recycle();
Chet Haased4307532014-12-01 06:32:38 -0800676 } else if (name.equals("propertyValuesHolder")) {
677 PropertyValuesHolder[] values = loadValues(res, theme, parser,
678 Xml.asAttributeSet(parser));
679 if (values != null && anim != null && (anim instanceof ValueAnimator)) {
680 ((ValueAnimator) anim).setValues(values);
681 }
682 gotValues = true;
ztenghuie5e92602014-06-03 14:02:10 -0700683 } else {
684 throw new RuntimeException("Unknown animator name: " + parser.getName());
685 }
686
Chet Haased4307532014-12-01 06:32:38 -0800687 if (parent != null && !gotValues) {
ztenghuie5e92602014-06-03 14:02:10 -0700688 if (childAnims == null) {
689 childAnims = new ArrayList<Animator>();
690 }
691 childAnims.add(anim);
692 }
693 }
694 if (parent != null && childAnims != null) {
695 Animator[] animsArray = new Animator[childAnims.size()];
696 int index = 0;
697 for (Animator a : childAnims) {
698 animsArray[index++] = a;
699 }
700 if (sequenceOrdering == TOGETHER) {
701 parent.playTogether(animsArray);
702 } else {
703 parent.playSequentially(animsArray);
704 }
705 }
ztenghuie5e92602014-06-03 14:02:10 -0700706 return anim;
Chet Haased4307532014-12-01 06:32:38 -0800707 }
ztenghuie5e92602014-06-03 14:02:10 -0700708
Chet Haased4307532014-12-01 06:32:38 -0800709 private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
710 XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
711 ArrayList<PropertyValuesHolder> values = null;
712
713 int type;
714 while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
715 type != XmlPullParser.END_DOCUMENT) {
716
717 if (type != XmlPullParser.START_TAG) {
718 parser.next();
719 continue;
720 }
721
722 String name = parser.getName();
723
724 if (name.equals("propertyValuesHolder")) {
725 TypedArray a;
726 if (theme != null) {
727 a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
728 } else {
729 a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
730 }
731 String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
732 int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
Doris Liu9032fa52015-04-09 20:11:22 -0700733 VALUE_TYPE_UNDEFINED);
734
Chet Haased4307532014-12-01 06:32:38 -0800735 PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
736 if (pvh == null) {
737 pvh = getPVH(a, valueType,
738 R.styleable.PropertyValuesHolder_valueFrom,
739 R.styleable.PropertyValuesHolder_valueTo, propertyName);
740 }
741 if (pvh != null) {
742 if (values == null) {
743 values = new ArrayList<PropertyValuesHolder>();
744 }
745 values.add(pvh);
746 }
747 a.recycle();
748 }
749
750 parser.next();
751 }
752
753 PropertyValuesHolder[] valuesArray = null;
754 if (values != null) {
755 int count = values.size();
756 valuesArray = new PropertyValuesHolder[count];
757 for (int i = 0; i < count; ++i) {
758 valuesArray[i] = values.get(i);
759 }
760 }
761 return valuesArray;
762 }
763
Doris Liua01fbf32015-04-21 18:27:26 -0700764 // When no value type is provided in keyframe, we need to infer the type from the value. i.e.
765 // if value is defined in the style of a color value, then the color type is returned.
766 // Otherwise, default float type is returned.
767 private static int inferValueTypeOfKeyframe(Resources res, Theme theme, AttributeSet attrs) {
768 int valueType;
769 TypedArray a;
770 if (theme != null) {
771 a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
772 } else {
773 a = res.obtainAttributes(attrs, R.styleable.Keyframe);
774 }
775
776 TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
777 boolean hasValue = (keyframeValue != null);
778 // When no value type is provided, check whether it's a color type first.
779 // If not, fall back to default value type (i.e. float type).
780 if (hasValue && isColorType(keyframeValue.type)) {
781 valueType = VALUE_TYPE_COLOR;
782 } else {
783 valueType = VALUE_TYPE_FLOAT;
784 }
785 a.recycle();
786 return valueType;
787 }
788
Doris Liu2787ceb2015-06-05 17:03:21 -0700789 private static int inferValueTypeFromValues(TypedArray styledAttributes, int valueFromId,
790 int valueToId) {
791 TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
792 boolean hasFrom = (tvFrom != null);
793 int fromType = hasFrom ? tvFrom.type : 0;
794 TypedValue tvTo = styledAttributes.peekValue(valueToId);
795 boolean hasTo = (tvTo != null);
796 int toType = hasTo ? tvTo.type : 0;
797
798 int valueType;
799 // Check whether it's color type. If not, fall back to default type (i.e. float type)
800 if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
801 valueType = VALUE_TYPE_COLOR;
802 } else {
803 valueType = VALUE_TYPE_FLOAT;
804 }
805 return valueType;
806 }
807
Chet Haased4307532014-12-01 06:32:38 -0800808 private static void dumpKeyframes(Object[] keyframes, String header) {
809 if (keyframes == null || keyframes.length == 0) {
810 return;
811 }
812 Log.d(TAG, header);
813 int count = keyframes.length;
814 for (int i = 0; i < count; ++i) {
815 Keyframe keyframe = (Keyframe) keyframes[i];
816 Log.d(TAG, "Keyframe " + i + ": fraction " +
817 (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " +
818 ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null"));
819 }
820 }
821
Doris Liu9032fa52015-04-09 20:11:22 -0700822 // Load property values holder if there are keyframes defined in it. Otherwise return null.
Chet Haased4307532014-12-01 06:32:38 -0800823 private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
824 String propertyName, int valueType)
825 throws XmlPullParserException, IOException {
826
827 PropertyValuesHolder value = null;
828 ArrayList<Keyframe> keyframes = null;
829
830 int type;
831 while ((type = parser.next()) != XmlPullParser.END_TAG &&
832 type != XmlPullParser.END_DOCUMENT) {
833 String name = parser.getName();
834 if (name.equals("keyframe")) {
Doris Liua01fbf32015-04-21 18:27:26 -0700835 if (valueType == VALUE_TYPE_UNDEFINED) {
836 valueType = inferValueTypeOfKeyframe(res, theme, Xml.asAttributeSet(parser));
837 }
Chet Haased4307532014-12-01 06:32:38 -0800838 Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
839 if (keyframe != null) {
840 if (keyframes == null) {
841 keyframes = new ArrayList<Keyframe>();
842 }
843 keyframes.add(keyframe);
844 }
845 parser.next();
846 }
847 }
848
849 int count;
850 if (keyframes != null && (count = keyframes.size()) > 0) {
851 // make sure we have keyframes at 0 and 1
852 // If we have keyframes with set fractions, add keyframes at start/end
853 // appropriately. If start/end have no set fractions:
854 // if there's only one keyframe, set its fraction to 1 and add one at 0
855 // if >1 keyframe, set the last fraction to 1, the first fraction to 0
856 Keyframe firstKeyframe = keyframes.get(0);
857 Keyframe lastKeyframe = keyframes.get(count - 1);
858 float endFraction = lastKeyframe.getFraction();
859 if (endFraction < 1) {
860 if (endFraction < 0) {
861 lastKeyframe.setFraction(1);
862 } else {
863 keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1));
864 ++count;
865 }
866 }
867 float startFraction = firstKeyframe.getFraction();
868 if (startFraction != 0) {
869 if (startFraction < 0) {
870 firstKeyframe.setFraction(0);
871 } else {
872 keyframes.add(0, createNewKeyframe(firstKeyframe, 0));
873 ++count;
874 }
875 }
876 Keyframe[] keyframeArray = new Keyframe[count];
877 keyframes.toArray(keyframeArray);
878 for (int i = 0; i < count; ++i) {
879 Keyframe keyframe = keyframeArray[i];
880 if (keyframe.getFraction() < 0) {
881 if (i == 0) {
882 keyframe.setFraction(0);
883 } else if (i == count - 1) {
884 keyframe.setFraction(1);
885 } else {
886 // figure out the start/end parameters of the current gap
887 // in fractions and distribute the gap among those keyframes
888 int startIndex = i;
889 int endIndex = i;
890 for (int j = startIndex + 1; j < count - 1; ++j) {
891 if (keyframeArray[j].getFraction() >= 0) {
892 break;
893 }
894 endIndex = j;
895 }
896 float gap = keyframeArray[endIndex + 1].getFraction() -
897 keyframeArray[startIndex - 1].getFraction();
898 distributeKeyframes(keyframeArray, gap, startIndex, endIndex);
899 }
900 }
901 }
902 value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray);
903 if (valueType == VALUE_TYPE_COLOR) {
904 value.setEvaluator(ArgbEvaluator.getInstance());
905 }
906 }
907
908 return value;
909 }
910
911 private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) {
912 return sampleKeyframe.getType() == float.class ?
913 Keyframe.ofFloat(fraction) :
914 (sampleKeyframe.getType() == int.class) ?
915 Keyframe.ofInt(fraction) :
916 Keyframe.ofObject(fraction);
917 }
918
919 /**
920 * Utility function to set fractions on keyframes to cover a gap in which the
921 * fractions are not currently set. Keyframe fractions will be distributed evenly
922 * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap
923 * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the
924 * keyframe before startIndex.
925 * Assumptions:
926 * - First and last keyframe fractions (bounding this spread) are already set. So,
927 * for example, if no fractions are set, we will already set first and last keyframe
928 * fraction values to 0 and 1.
929 * - startIndex must be >0 (which follows from first assumption).
930 * - endIndex must be >= startIndex.
931 *
932 * @param keyframes the array of keyframes
933 * @param gap The total gap we need to distribute
934 * @param startIndex The index of the first keyframe whose fraction must be set
935 * @param endIndex The index of the last keyframe whose fraction must be set
936 */
937 private static void distributeKeyframes(Keyframe[] keyframes, float gap,
938 int startIndex, int endIndex) {
939 int count = endIndex - startIndex + 2;
940 float increment = gap / count;
941 for (int i = startIndex; i <= endIndex; ++i) {
942 keyframes[i].setFraction(keyframes[i-1].getFraction() + increment);
943 }
944 }
945
946 private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
947 int valueType)
948 throws XmlPullParserException, IOException {
949
950 TypedArray a;
951 if (theme != null) {
952 a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
953 } else {
954 a = res.obtainAttributes(attrs, R.styleable.Keyframe);
955 }
956
957 Keyframe keyframe = null;
958
959 float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
960
Doris Liu9032fa52015-04-09 20:11:22 -0700961 TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
962 boolean hasValue = (keyframeValue != null);
963 if (valueType == VALUE_TYPE_UNDEFINED) {
964 // When no value type is provided, check whether it's a color type first.
965 // If not, fall back to default value type (i.e. float type).
966 if (hasValue && isColorType(keyframeValue.type)) {
967 valueType = VALUE_TYPE_COLOR;
968 } else {
969 valueType = VALUE_TYPE_FLOAT;
970 }
971 }
Chet Haased4307532014-12-01 06:32:38 -0800972
973 if (hasValue) {
974 switch (valueType) {
975 case VALUE_TYPE_FLOAT:
976 float value = a.getFloat(R.styleable.Keyframe_value, 0);
977 keyframe = Keyframe.ofFloat(fraction, value);
978 break;
979 case VALUE_TYPE_COLOR:
980 case VALUE_TYPE_INT:
981 int intValue = a.getInt(R.styleable.Keyframe_value, 0);
982 keyframe = Keyframe.ofInt(fraction, intValue);
983 break;
984 }
985 } else {
986 keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
987 Keyframe.ofInt(fraction);
988 }
989
Doris Liu6aac06a2015-04-01 10:27:40 -0700990 final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0);
991 if (resID > 0) {
992 final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
993 keyframe.setInterpolator(interpolator);
994 }
Chet Haased4307532014-12-01 06:32:38 -0800995 a.recycle();
996
997 return keyframe;
ztenghuie5e92602014-06-03 14:02:10 -0700998 }
999
George Mountfd3c4742014-09-08 13:23:36 -07001000 private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs,
Doris Liu7513aab2015-04-18 00:14:14 +00001001 float pathErrorScale) throws NotFoundException {
ztenghuie5e92602014-06-03 14:02:10 -07001002 ObjectAnimator anim = new ObjectAnimator();
1003
Doris Liu7513aab2015-04-18 00:14:14 +00001004 loadAnimator(res, theme, attrs, anim, pathErrorScale);
ztenghuie5e92602014-06-03 14:02:10 -07001005
ztenghuie5e92602014-06-03 14:02:10 -07001006 return anim;
1007 }
1008
1009 /**
1010 * Creates a new animation whose parameters come from the specified context
1011 * and attributes set.
1012 *
1013 * @param res The resources
1014 * @param attrs The set of attributes holding the animation parameters
ztenghuicf4832f2014-06-17 09:54:45 -07001015 * @param anim Null if this is a ValueAnimator, otherwise this is an
1016 * ObjectAnimator
ztenghuie5e92602014-06-03 14:02:10 -07001017 */
1018 private static ValueAnimator loadAnimator(Resources res, Theme theme,
Doris Liu7513aab2015-04-18 00:14:14 +00001019 AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
ztenghuie5e92602014-06-03 14:02:10 -07001020 throws NotFoundException {
ztenghuicf4832f2014-06-17 09:54:45 -07001021 TypedArray arrayAnimator = null;
1022 TypedArray arrayObjectAnimator = null;
1023
ztenghuie5e92602014-06-03 14:02:10 -07001024 if (theme != null) {
ztenghuicf4832f2014-06-17 09:54:45 -07001025 arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
ztenghuie5e92602014-06-03 14:02:10 -07001026 } else {
ztenghuicf4832f2014-06-17 09:54:45 -07001027 arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
ztenghuie5e92602014-06-03 14:02:10 -07001028 }
1029
ztenghuicf4832f2014-06-17 09:54:45 -07001030 // If anim is not null, then it is an object animator.
1031 if (anim != null) {
1032 if (theme != null) {
1033 arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
1034 R.styleable.PropertyAnimator, 0, 0);
1035 } else {
1036 arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
1037 }
Yigit Boyard422dc32014-09-25 12:23:35 -07001038 anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
ztenghuicf4832f2014-06-17 09:54:45 -07001039 }
Craig Stout7f9988f2014-08-07 15:25:04 -07001040
1041 if (anim == null) {
1042 anim = new ValueAnimator();
1043 }
Yigit Boyard422dc32014-09-25 12:23:35 -07001044 anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
Craig Stout7f9988f2014-08-07 15:25:04 -07001045
George Mountfd3c4742014-09-08 13:23:36 -07001046 parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
Chet Haased51d3682010-08-11 19:46:48 -07001047
Yigit Boyard422dc32014-09-25 12:23:35 -07001048 final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
Chet Haased51d3682010-08-11 19:46:48 -07001049 if (resID > 0) {
Yigit Boyard422dc32014-09-25 12:23:35 -07001050 final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
1051 if (interpolator instanceof BaseInterpolator) {
1052 anim.appendChangingConfigurations(
1053 ((BaseInterpolator) interpolator).getChangingConfiguration());
1054 }
1055 anim.setInterpolator(interpolator);
Chet Haased51d3682010-08-11 19:46:48 -07001056 }
ztenghuicf4832f2014-06-17 09:54:45 -07001057
1058 arrayAnimator.recycle();
Craig Stout7f9988f2014-08-07 15:25:04 -07001059 if (arrayObjectAnimator != null) {
1060 arrayObjectAnimator.recycle();
1061 }
Chet Haased51d3682010-08-11 19:46:48 -07001062 return anim;
1063 }
Yigit Boyard422dc32014-09-25 12:23:35 -07001064
1065 private static int getChangingConfigs(Resources resources, int id) {
1066 synchronized (sTmpTypedValue) {
1067 resources.getValue(id, sTmpTypedValue, true);
1068 return sTmpTypedValue.changingConfigurations;
1069 }
1070 }
Doris Liu9032fa52015-04-09 20:11:22 -07001071
1072 private static boolean isColorType(int type) {
1073 return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
1074 }
Chet Haased51d3682010-08-11 19:46:48 -07001075}