blob: 7d5931fc1cc28527a285b6d2383a1b59de04225b [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;
Alan Viveretteac85f902016-03-11 15:15:51 -050019import android.annotation.AnyRes;
20import android.annotation.NonNull;
Chet Haased51d3682010-08-11 19:46:48 -070021import android.content.Context;
Alan Viveretteac85f902016-03-11 15:15:51 -050022import android.content.pm.ActivityInfo.Config;
Yigit Boyard422dc32014-09-25 12:23:35 -070023import android.content.res.ConfigurationBoundResourceCache;
24import android.content.res.ConstantState;
Chet Haased51d3682010-08-11 19:46:48 -070025import android.content.res.Resources;
ztenghuicf4832f2014-06-17 09:54:45 -070026import android.content.res.Resources.NotFoundException;
ztenghuie5e92602014-06-03 14:02:10 -070027import android.content.res.Resources.Theme;
Chet Haased51d3682010-08-11 19:46:48 -070028import android.content.res.TypedArray;
29import android.content.res.XmlResourceParser;
ztenghuicf4832f2014-06-17 09:54:45 -070030import android.graphics.Path;
Chet Haased51d3682010-08-11 19:46:48 -070031import android.util.AttributeSet;
ztenghuieb034fb2014-06-09 13:14:19 -070032import android.util.Log;
ztenghuicf4832f2014-06-17 09:54:45 -070033import android.util.PathParser;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -070034import android.util.StateSet;
Chet Haase5bed88e12010-11-30 14:43:33 -080035import android.util.TypedValue;
Chet Haased51d3682010-08-11 19:46:48 -070036import android.util.Xml;
ztenghuieb034fb2014-06-09 13:14:19 -070037import android.view.InflateException;
Chet Haased51d3682010-08-11 19:46:48 -070038import android.view.animation.AnimationUtils;
Yigit Boyard422dc32014-09-25 12:23:35 -070039import android.view.animation.BaseInterpolator;
40import android.view.animation.Interpolator;
ztenghuie5e92602014-06-03 14:02:10 -070041
42import com.android.internal.R;
43
Chet Haased51d3682010-08-11 19:46:48 -070044import org.xmlpull.v1.XmlPullParser;
45import org.xmlpull.v1.XmlPullParserException;
46
47import java.io.IOException;
48import java.util.ArrayList;
49
50/**
Chet Haase6cfdf452011-04-22 16:42:10 -070051 * This class is used to instantiate animator XML files into Animator objects.
Chet Haased51d3682010-08-11 19:46:48 -070052 * <p>
Chet Haase6cfdf452011-04-22 16:42:10 -070053 * For performance reasons, inflation relies heavily on pre-processing of
Chet Haased51d3682010-08-11 19:46:48 -070054 * XML files that is done at build time. Therefore, it is not currently possible
Chet Haase6cfdf452011-04-22 16:42:10 -070055 * to use this inflater with an XmlPullParser over a plain XML file at runtime;
Chet Haased51d3682010-08-11 19:46:48 -070056 * it only works with an XmlPullParser returned from a compiled resource (R.
57 * <em>something</em> file.)
58 */
Chet Haasea18a86b2010-09-07 13:20:00 -070059public class AnimatorInflater {
ztenghuieb034fb2014-06-09 13:14:19 -070060 private static final String TAG = "AnimatorInflater";
Chet Haased51d3682010-08-11 19:46:48 -070061 /**
Chet Haasea18a86b2010-09-07 13:20:00 -070062 * These flags are used when parsing AnimatorSet objects
Chet Haased51d3682010-08-11 19:46:48 -070063 */
64 private static final int TOGETHER = 0;
65 private static final int SEQUENTIALLY = 1;
66
67 /**
68 * Enum values used in XML attributes to indicate the value for mValueType
69 */
70 private static final int VALUE_TYPE_FLOAT = 0;
71 private static final int VALUE_TYPE_INT = 1;
ztenghuieb034fb2014-06-09 13:14:19 -070072 private static final int VALUE_TYPE_PATH = 2;
Chet Haased4307532014-12-01 06:32:38 -080073 private static final int VALUE_TYPE_COLOR = 3;
Doris Liu9032fa52015-04-09 20:11:22 -070074 private static final int VALUE_TYPE_UNDEFINED = 4;
Chet Haased51d3682010-08-11 19:46:48 -070075
ztenghuieb034fb2014-06-09 13:14:19 -070076 private static final boolean DBG_ANIMATOR_INFLATER = false;
77
Yigit Boyard422dc32014-09-25 12:23:35 -070078 // used to calculate changing configs for resource references
79 private static final TypedValue sTmpTypedValue = new TypedValue();
80
Chet Haased51d3682010-08-11 19:46:48 -070081 /**
Chet Haasea18a86b2010-09-07 13:20:00 -070082 * Loads an {@link Animator} object from a resource
Chet Haased51d3682010-08-11 19:46:48 -070083 *
Doris Liu7513aab2015-04-18 00:14:14 +000084 * @param context Application context used to access resources
Chet Haased51d3682010-08-11 19:46:48 -070085 * @param id The resource id of the animation to load
Chet Haasea18a86b2010-09-07 13:20:00 -070086 * @return The animator object reference by the specified id
Chet Haased51d3682010-08-11 19:46:48 -070087 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
88 */
Tor Norbye7b9c9122013-05-30 16:48:33 -070089 public static Animator loadAnimator(Context context, @AnimatorRes int id)
Chet Haased51d3682010-08-11 19:46:48 -070090 throws NotFoundException {
Doris Liu7513aab2015-04-18 00:14:14 +000091 return loadAnimator(context.getResources(), context.getTheme(), id);
ztenghuie5e92602014-06-03 14:02:10 -070092 }
93
94 /**
95 * Loads an {@link Animator} object from a resource
96 *
Doris Liu7513aab2015-04-18 00:14:14 +000097 * @param resources The resources
ztenghuie5e92602014-06-03 14:02:10 -070098 * @param theme The theme
99 * @param id The resource id of the animation to load
100 * @return The animator object reference by the specified id
101 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
102 * @hide
103 */
Doris Liu7513aab2015-04-18 00:14:14 +0000104 public static Animator loadAnimator(Resources resources, Theme theme, int id)
ztenghuie5e92602014-06-03 14:02:10 -0700105 throws NotFoundException {
Doris Liu7513aab2015-04-18 00:14:14 +0000106 return loadAnimator(resources, theme, id, 1);
George Mountfd3c4742014-09-08 13:23:36 -0700107 }
108
109 /** @hide */
110 public static Animator loadAnimator(Resources resources, Theme theme, int id,
111 float pathErrorScale) throws NotFoundException {
Yigit Boyard422dc32014-09-25 12:23:35 -0700112 final ConfigurationBoundResourceCache<Animator> animatorCache = resources
113 .getAnimatorCache();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800114 Animator animator = animatorCache.getInstance(id, resources, theme);
Yigit Boyard422dc32014-09-25 12:23:35 -0700115 if (animator != null) {
116 if (DBG_ANIMATOR_INFLATER) {
117 Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
118 }
119 return animator;
120 } else if (DBG_ANIMATOR_INFLATER) {
121 Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
122 }
Chet Haased51d3682010-08-11 19:46:48 -0700123 XmlResourceParser parser = null;
124 try {
ztenghuie5e92602014-06-03 14:02:10 -0700125 parser = resources.getAnimation(id);
Doris Liu7513aab2015-04-18 00:14:14 +0000126 animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
Yigit Boyard422dc32014-09-25 12:23:35 -0700127 if (animator != null) {
128 animator.appendChangingConfigurations(getChangingConfigs(resources, id));
129 final ConstantState<Animator> constantState = animator.createConstantState();
130 if (constantState != null) {
131 if (DBG_ANIMATOR_INFLATER) {
132 Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
133 }
134 animatorCache.put(id, theme, constantState);
135 // create a new animator so that cached version is never used by the user
136 animator = constantState.newInstance(resources, theme);
137 }
138 }
139 return animator;
Chet Haased51d3682010-08-11 19:46:48 -0700140 } catch (XmlPullParserException ex) {
141 Resources.NotFoundException rnf =
142 new Resources.NotFoundException("Can't load animation resource ID #0x" +
George Mountfd3c4742014-09-08 13:23:36 -0700143 Integer.toHexString(id));
Chet Haased51d3682010-08-11 19:46:48 -0700144 rnf.initCause(ex);
145 throw rnf;
146 } catch (IOException ex) {
147 Resources.NotFoundException rnf =
148 new Resources.NotFoundException("Can't load animation resource ID #0x" +
George Mountfd3c4742014-09-08 13:23:36 -0700149 Integer.toHexString(id));
Chet Haased51d3682010-08-11 19:46:48 -0700150 rnf.initCause(ex);
151 throw rnf;
152 } finally {
153 if (parser != null) parser.close();
154 }
155 }
156
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700157 public static StateListAnimator loadStateListAnimator(Context context, int id)
158 throws NotFoundException {
Yigit Boyard422dc32014-09-25 12:23:35 -0700159 final Resources resources = context.getResources();
160 final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
161 .getStateListAnimatorCache();
162 final Theme theme = context.getTheme();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800163 StateListAnimator animator = cache.getInstance(id, resources, theme);
Yigit Boyard422dc32014-09-25 12:23:35 -0700164 if (animator != null) {
165 return animator;
166 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700167 XmlResourceParser parser = null;
168 try {
Yigit Boyard422dc32014-09-25 12:23:35 -0700169 parser = resources.getAnimation(id);
170 animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
171 if (animator != null) {
172 animator.appendChangingConfigurations(getChangingConfigs(resources, id));
173 final ConstantState<StateListAnimator> constantState = animator
174 .createConstantState();
175 if (constantState != null) {
176 cache.put(id, theme, constantState);
177 // return a clone so that the animator in constant state is never used.
178 animator = constantState.newInstance(resources, theme);
179 }
180 }
181 return animator;
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700182 } catch (XmlPullParserException ex) {
183 Resources.NotFoundException rnf =
184 new Resources.NotFoundException(
185 "Can't load state list animator resource ID #0x" +
186 Integer.toHexString(id)
187 );
188 rnf.initCause(ex);
189 throw rnf;
190 } catch (IOException ex) {
191 Resources.NotFoundException rnf =
192 new Resources.NotFoundException(
193 "Can't load state list animator resource ID #0x" +
194 Integer.toHexString(id)
195 );
196 rnf.initCause(ex);
197 throw rnf;
198 } finally {
199 if (parser != null) {
200 parser.close();
201 }
202 }
203 }
204
205 private static StateListAnimator createStateListAnimatorFromXml(Context context,
206 XmlPullParser parser, AttributeSet attributeSet)
207 throws IOException, XmlPullParserException {
208 int type;
209 StateListAnimator stateListAnimator = new StateListAnimator();
210
211 while (true) {
212 type = parser.next();
213 switch (type) {
214 case XmlPullParser.END_DOCUMENT:
215 case XmlPullParser.END_TAG:
216 return stateListAnimator;
217
218 case XmlPullParser.START_TAG:
219 // parse item
220 Animator animator = null;
221 if ("item".equals(parser.getName())) {
222 int attributeCount = parser.getAttributeCount();
223 int[] states = new int[attributeCount];
224 int stateIndex = 0;
225 for (int i = 0; i < attributeCount; i++) {
226 int attrName = attributeSet.getAttributeNameResource(i);
ztenghuicf4832f2014-06-17 09:54:45 -0700227 if (attrName == R.attr.animation) {
Yigit Boyard422dc32014-09-25 12:23:35 -0700228 final int animId = attributeSet.getAttributeResourceValue(i, 0);
229 animator = loadAnimator(context, animId);
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700230 } else {
231 states[stateIndex++] =
232 attributeSet.getAttributeBooleanValue(i, false) ?
233 attrName : -attrName;
234 }
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700235 }
236 if (animator == null) {
ztenghuie5e92602014-06-03 14:02:10 -0700237 animator = createAnimatorFromXml(context.getResources(),
Doris Liu7513aab2015-04-18 00:14:14 +0000238 context.getTheme(), parser, 1f);
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700239 }
240
241 if (animator == null) {
242 throw new Resources.NotFoundException(
243 "animation state item must have a valid animation");
244 }
245 stateListAnimator
246 .addState(StateSet.trimStateSet(states, stateIndex), animator);
Yigit Boyarf4c5bf32014-05-06 16:56:08 -0700247 }
248 break;
249 }
250 }
251 }
252
ztenghuicf4832f2014-06-17 09:54:45 -0700253 /**
ztenghuieb034fb2014-06-09 13:14:19 -0700254 * PathDataEvaluator is used to interpolate between two paths which are
255 * represented in the same format but different control points' values.
Doris Liu804618d2015-11-16 22:48:34 -0800256 * The path is represented as verbs and points for each of the verbs.
ztenghuieb034fb2014-06-09 13:14:19 -0700257 */
Doris Liu804618d2015-11-16 22:48:34 -0800258 private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
259 private final PathParser.PathData mPathData = new PathParser.PathData();
ztenghuieb034fb2014-06-09 13:14:19 -0700260
261 @Override
Doris Liu804618d2015-11-16 22:48:34 -0800262 public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
263 PathParser.PathData endPathData) {
264 if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
ztenghuieb034fb2014-06-09 13:14:19 -0700265 throw new IllegalArgumentException("Can't interpolate between"
266 + " two incompatible pathData");
267 }
Doris Liu804618d2015-11-16 22:48:34 -0800268 return mPathData;
ztenghuieb034fb2014-06-09 13:14:19 -0700269 }
270 }
271
Chet Haased4307532014-12-01 06:32:38 -0800272 private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
273 int valueFromId, int valueToId, String propertyName) {
274
Chet Haased4307532014-12-01 06:32:38 -0800275 TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
276 boolean hasFrom = (tvFrom != null);
277 int fromType = hasFrom ? tvFrom.type : 0;
278 TypedValue tvTo = styledAttributes.peekValue(valueToId);
279 boolean hasTo = (tvTo != null);
280 int toType = hasTo ? tvTo.type : 0;
281
Doris Liu9032fa52015-04-09 20:11:22 -0700282 if (valueType == VALUE_TYPE_UNDEFINED) {
283 // Check whether it's color type. If not, fall back to default type (i.e. float type)
284 if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
285 valueType = VALUE_TYPE_COLOR;
286 } else {
287 valueType = VALUE_TYPE_FLOAT;
288 }
289 }
290
291 boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
292
Chet Haased4307532014-12-01 06:32:38 -0800293 PropertyValuesHolder returnValue = null;
294
295 if (valueType == VALUE_TYPE_PATH) {
296 String fromString = styledAttributes.getString(valueFromId);
297 String toString = styledAttributes.getString(valueToId);
Doris Liu804618d2015-11-16 22:48:34 -0800298 PathParser.PathData nodesFrom = fromString == null
299 ? null : new PathParser.PathData(fromString);
300 PathParser.PathData nodesTo = toString == null
301 ? null : new PathParser.PathData(toString);
Chet Haased4307532014-12-01 06:32:38 -0800302
303 if (nodesFrom != null || nodesTo != null) {
304 if (nodesFrom != null) {
Doris Liu804618d2015-11-16 22:48:34 -0800305 TypeEvaluator evaluator = new PathDataEvaluator();
Chet Haased4307532014-12-01 06:32:38 -0800306 if (nodesTo != null) {
307 if (!PathParser.canMorph(nodesFrom, nodesTo)) {
308 throw new InflateException(" Can't morph from " + fromString + " to " +
309 toString);
310 }
311 returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
312 nodesFrom, nodesTo);
313 } else {
314 returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
315 (Object) nodesFrom);
316 }
317 } else if (nodesTo != null) {
Doris Liu804618d2015-11-16 22:48:34 -0800318 TypeEvaluator evaluator = new PathDataEvaluator();
Chet Haased4307532014-12-01 06:32:38 -0800319 returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
320 (Object) nodesTo);
321 }
322 }
323 } else {
324 TypeEvaluator evaluator = null;
325 // Integer and float value types are handled here.
Doris Liu9032fa52015-04-09 20:11:22 -0700326 if (valueType == VALUE_TYPE_COLOR) {
Chet Haased4307532014-12-01 06:32:38 -0800327 // special case for colors: ignore valueType and get ints
Chet Haased4307532014-12-01 06:32:38 -0800328 evaluator = ArgbEvaluator.getInstance();
329 }
330 if (getFloats) {
331 float valueFrom;
332 float valueTo;
333 if (hasFrom) {
334 if (fromType == TypedValue.TYPE_DIMENSION) {
335 valueFrom = styledAttributes.getDimension(valueFromId, 0f);
336 } else {
337 valueFrom = styledAttributes.getFloat(valueFromId, 0f);
338 }
339 if (hasTo) {
340 if (toType == TypedValue.TYPE_DIMENSION) {
341 valueTo = styledAttributes.getDimension(valueToId, 0f);
342 } else {
343 valueTo = styledAttributes.getFloat(valueToId, 0f);
344 }
345 returnValue = PropertyValuesHolder.ofFloat(propertyName,
346 valueFrom, valueTo);
347 } else {
348 returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom);
349 }
350 } else {
351 if (toType == TypedValue.TYPE_DIMENSION) {
352 valueTo = styledAttributes.getDimension(valueToId, 0f);
353 } else {
354 valueTo = styledAttributes.getFloat(valueToId, 0f);
355 }
356 returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo);
357 }
358 } else {
359 int valueFrom;
360 int valueTo;
361 if (hasFrom) {
362 if (fromType == TypedValue.TYPE_DIMENSION) {
363 valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700364 } else if (isColorType(fromType)) {
Chet Haased4307532014-12-01 06:32:38 -0800365 valueFrom = styledAttributes.getColor(valueFromId, 0);
366 } else {
367 valueFrom = styledAttributes.getInt(valueFromId, 0);
368 }
369 if (hasTo) {
370 if (toType == TypedValue.TYPE_DIMENSION) {
371 valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700372 } else if (isColorType(toType)) {
Chet Haased4307532014-12-01 06:32:38 -0800373 valueTo = styledAttributes.getColor(valueToId, 0);
374 } else {
375 valueTo = styledAttributes.getInt(valueToId, 0);
376 }
377 returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo);
378 } else {
379 returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom);
380 }
381 } else {
382 if (hasTo) {
383 if (toType == TypedValue.TYPE_DIMENSION) {
384 valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700385 } else if (isColorType(toType)) {
Chet Haased4307532014-12-01 06:32:38 -0800386 valueTo = styledAttributes.getColor(valueToId, 0);
387 } else {
388 valueTo = styledAttributes.getInt(valueToId, 0);
389 }
390 returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo);
391 }
392 }
393 }
394 if (returnValue != null && evaluator != null) {
395 returnValue.setEvaluator(evaluator);
396 }
397 }
398
399 return returnValue;
400 }
401
ztenghuieb034fb2014-06-09 13:14:19 -0700402 /**
Craig Stout7f9988f2014-08-07 15:25:04 -0700403 * @param anim The animator, must not be null
ztenghuicf4832f2014-06-17 09:54:45 -0700404 * @param arrayAnimator Incoming typed array for Animator's attributes.
405 * @param arrayObjectAnimator Incoming typed array for Object Animator's
406 * attributes.
George Mountfd3c4742014-09-08 13:23:36 -0700407 * @param pixelSize The relative pixel size, used to calculate the
408 * maximum error for path animations.
ztenghuicf4832f2014-06-17 09:54:45 -0700409 */
410 private static void parseAnimatorFromTypeArray(ValueAnimator anim,
George Mountfd3c4742014-09-08 13:23:36 -0700411 TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
ztenghuicf4832f2014-06-17 09:54:45 -0700412 long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
Chet Haased51d3682010-08-11 19:46:48 -0700413
ztenghuicf4832f2014-06-17 09:54:45 -0700414 long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
Chet Haased51d3682010-08-11 19:46:48 -0700415
Doris Liu2787ceb2015-06-05 17:03:21 -0700416 int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_UNDEFINED);
Chet Haased51d3682010-08-11 19:46:48 -0700417
Doris Liu2787ceb2015-06-05 17:03:21 -0700418 if (valueType == VALUE_TYPE_UNDEFINED) {
419 valueType = inferValueTypeFromValues(arrayAnimator, R.styleable.Animator_valueFrom,
420 R.styleable.Animator_valueTo);
421 }
Chet Haased4307532014-12-01 06:32:38 -0800422 PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType,
423 R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, "");
424 if (pvh != null) {
425 anim.setValues(pvh);
Chet Haase5bed88e12010-11-30 14:43:33 -0800426 }
427
ztenghuieb034fb2014-06-09 13:14:19 -0700428 anim.setDuration(duration);
429 anim.setStartDelay(startDelay);
430
431 if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
432 anim.setRepeatCount(
433 arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
434 }
435 if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
436 anim.setRepeatMode(
437 arrayAnimator.getInt(R.styleable.Animator_repeatMode,
438 ValueAnimator.RESTART));
439 }
ztenghuieb034fb2014-06-09 13:14:19 -0700440
441 if (arrayObjectAnimator != null) {
Chet Haased4307532014-12-01 06:32:38 -0800442 setupObjectAnimator(anim, arrayObjectAnimator, valueType == VALUE_TYPE_FLOAT,
443 pixelSize);
ztenghuieb034fb2014-06-09 13:14:19 -0700444 }
445 }
446
447 /**
448 * Setup the Animator to achieve path morphing.
449 *
450 * @param anim The target Animator which will be updated.
451 * @param arrayAnimator TypedArray for the ValueAnimator.
452 * @return the PathDataEvaluator.
453 */
454 private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim,
455 TypedArray arrayAnimator) {
456 TypeEvaluator evaluator = null;
457 String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
458 String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
Doris Liu804618d2015-11-16 22:48:34 -0800459 PathParser.PathData pathDataFrom = fromString == null
460 ? null : new PathParser.PathData(fromString);
461 PathParser.PathData pathDataTo = toString == null
462 ? null : new PathParser.PathData(toString);
ztenghuieb034fb2014-06-09 13:14:19 -0700463
Doris Liu804618d2015-11-16 22:48:34 -0800464 if (pathDataFrom != null) {
465 if (pathDataTo != null) {
466 anim.setObjectValues(pathDataFrom, pathDataTo);
467 if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
ztenghuieb034fb2014-06-09 13:14:19 -0700468 throw new InflateException(arrayAnimator.getPositionDescription()
469 + " Can't morph from " + fromString + " to " + toString);
470 }
471 } else {
Doris Liu804618d2015-11-16 22:48:34 -0800472 anim.setObjectValues((Object)pathDataFrom);
ztenghuieb034fb2014-06-09 13:14:19 -0700473 }
Doris Liu804618d2015-11-16 22:48:34 -0800474 evaluator = new PathDataEvaluator();
475 } else if (pathDataTo != null) {
476 anim.setObjectValues((Object)pathDataTo);
477 evaluator = new PathDataEvaluator();
ztenghuieb034fb2014-06-09 13:14:19 -0700478 }
479
480 if (DBG_ANIMATOR_INFLATER && evaluator != null) {
481 Log.v(TAG, "create a new PathDataEvaluator here");
482 }
483
484 return evaluator;
485 }
486
487 /**
488 * Setup ObjectAnimator's property or values from pathData.
489 *
490 * @param anim The target Animator which will be updated.
491 * @param arrayObjectAnimator TypedArray for the ObjectAnimator.
492 * @param getFloats True if the value type is float.
George Mountfd3c4742014-09-08 13:23:36 -0700493 * @param pixelSize The relative pixel size, used to calculate the
494 * maximum error for path animations.
ztenghuieb034fb2014-06-09 13:14:19 -0700495 */
496 private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator,
George Mountfd3c4742014-09-08 13:23:36 -0700497 boolean getFloats, float pixelSize) {
ztenghuieb034fb2014-06-09 13:14:19 -0700498 ObjectAnimator oa = (ObjectAnimator) anim;
499 String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
500
Doris Liu2787ceb2015-06-05 17:03:21 -0700501 // Path can be involved in an ObjectAnimator in the following 3 ways:
502 // 1) Path morphing: the property to be animated is pathData, and valueFrom and valueTo
503 // are both of pathType. valueType = pathType needs to be explicitly defined.
504 // 2) A property in X or Y dimension can be animated along a path: the property needs to be
505 // defined in propertyXName or propertyYName attribute, the path will be defined in the
506 // pathData attribute. valueFrom and valueTo will not be necessary for this animation.
507 // 3) PathInterpolator can also define a path (in pathData) for its interpolation curve.
508 // Here we are dealing with case 2:
ztenghuieb034fb2014-06-09 13:14:19 -0700509 if (pathData != null) {
510 String propertyXName =
511 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
512 String propertyYName =
513 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
514
515 if (propertyXName == null && propertyYName == null) {
516 throw new InflateException(arrayObjectAnimator.getPositionDescription()
517 + " propertyXName or propertyYName is needed for PathData");
518 } else {
519 Path path = PathParser.createPathFromPathData(pathData);
George Mountfd3c4742014-09-08 13:23:36 -0700520 float error = 0.5f * pixelSize; // max half a pixel error
521 PathKeyframes keyframeSet = KeyframeSet.ofPath(path, error);
George Mount984011f2014-08-21 14:28:01 -0700522 Keyframes xKeyframes;
523 Keyframes yKeyframes;
524 if (getFloats) {
525 xKeyframes = keyframeSet.createXFloatKeyframes();
526 yKeyframes = keyframeSet.createYFloatKeyframes();
527 } else {
528 xKeyframes = keyframeSet.createXIntKeyframes();
529 yKeyframes = keyframeSet.createYIntKeyframes();
530 }
ztenghuieb034fb2014-06-09 13:14:19 -0700531 PropertyValuesHolder x = null;
532 PropertyValuesHolder y = null;
533 if (propertyXName != null) {
George Mount984011f2014-08-21 14:28:01 -0700534 x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes);
ztenghuieb034fb2014-06-09 13:14:19 -0700535 }
536 if (propertyYName != null) {
George Mount984011f2014-08-21 14:28:01 -0700537 y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes);
ztenghuieb034fb2014-06-09 13:14:19 -0700538 }
539 if (x == null) {
540 oa.setValues(y);
541 } else if (y == null) {
542 oa.setValues(x);
543 } else {
544 oa.setValues(x, y);
545 }
546 }
547 } else {
548 String propertyName =
549 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
550 oa.setPropertyName(propertyName);
551 }
552 }
553
554 /**
555 * Setup ValueAnimator's values.
556 * This will handle all of the integer, float and color types.
557 *
558 * @param anim The target Animator which will be updated.
559 * @param arrayAnimator TypedArray for the ValueAnimator.
560 * @param getFloats True if the value type is float.
561 * @param hasFrom True if "valueFrom" exists.
562 * @param fromType The type of "valueFrom".
563 * @param hasTo True if "valueTo" exists.
564 * @param toType The type of "valueTo".
565 */
566 private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator,
567 boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) {
568 int valueFromIndex = R.styleable.Animator_valueFrom;
569 int valueToIndex = R.styleable.Animator_valueTo;
Chet Haase5bed88e12010-11-30 14:43:33 -0800570 if (getFloats) {
571 float valueFrom;
572 float valueTo;
573 if (hasFrom) {
574 if (fromType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700575 valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
Chet Haase2794eb32010-10-12 16:29:28 -0700576 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700577 valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
Romain Guy83d6e822010-10-14 10:13:53 -0700578 }
Chet Haase5bed88e12010-11-30 14:43:33 -0800579 if (hasTo) {
580 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700581 valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
Chet Haase2794eb32010-10-12 16:29:28 -0700582 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700583 valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
Chet Haase2794eb32010-10-12 16:29:28 -0700584 }
Chet Haase5bed88e12010-11-30 14:43:33 -0800585 anim.setFloatValues(valueFrom, valueTo);
Chet Haase2794eb32010-10-12 16:29:28 -0700586 } else {
Chet Haase5bed88e12010-11-30 14:43:33 -0800587 anim.setFloatValues(valueFrom);
588 }
589 } else {
590 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700591 valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
Chet Haase5bed88e12010-11-30 14:43:33 -0800592 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700593 valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
Chet Haase5bed88e12010-11-30 14:43:33 -0800594 }
595 anim.setFloatValues(valueTo);
596 }
597 } else {
598 int valueFrom;
599 int valueTo;
600 if (hasFrom) {
601 if (fromType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700602 valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700603 } else if (isColorType(fromType)) {
ztenghuicf4832f2014-06-17 09:54:45 -0700604 valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800605 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700606 valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800607 }
608 if (hasTo) {
609 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700610 valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700611 } else if (isColorType(toType)) {
ztenghuicf4832f2014-06-17 09:54:45 -0700612 valueTo = arrayAnimator.getColor(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800613 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700614 valueTo = arrayAnimator.getInt(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800615 }
616 anim.setIntValues(valueFrom, valueTo);
617 } else {
618 anim.setIntValues(valueFrom);
619 }
620 } else {
621 if (hasTo) {
622 if (toType == TypedValue.TYPE_DIMENSION) {
ztenghuicf4832f2014-06-17 09:54:45 -0700623 valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
Doris Liu9032fa52015-04-09 20:11:22 -0700624 } else if (isColorType(toType)) {
ztenghuicf4832f2014-06-17 09:54:45 -0700625 valueTo = arrayAnimator.getColor(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800626 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700627 valueTo = arrayAnimator.getInt(valueToIndex, 0);
Chet Haase5bed88e12010-11-30 14:43:33 -0800628 }
Chet Haase2794eb32010-10-12 16:29:28 -0700629 anim.setIntValues(valueTo);
Chet Haased51d3682010-08-11 19:46:48 -0700630 }
Chet Haase2794eb32010-10-12 16:29:28 -0700631 }
Chet Haased51d3682010-08-11 19:46:48 -0700632 }
ztenghuie5e92602014-06-03 14:02:10 -0700633 }
634
George Mountfd3c4742014-09-08 13:23:36 -0700635 private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
Doris Liu7513aab2015-04-18 00:14:14 +0000636 float pixelSize)
ztenghuie5e92602014-06-03 14:02:10 -0700637 throws XmlPullParserException, IOException {
George Mountfd3c4742014-09-08 13:23:36 -0700638 return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0,
Doris Liu7513aab2015-04-18 00:14:14 +0000639 pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700640 }
641
642 private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
Doris Liu7513aab2015-04-18 00:14:14 +0000643 AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
644 throws XmlPullParserException, IOException {
ztenghuie5e92602014-06-03 14:02:10 -0700645 Animator anim = null;
646 ArrayList<Animator> childAnims = null;
647
648 // Make sure we are on a start tag.
649 int type;
650 int depth = parser.getDepth();
651
652 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
653 && type != XmlPullParser.END_DOCUMENT) {
654
655 if (type != XmlPullParser.START_TAG) {
656 continue;
657 }
658
659 String name = parser.getName();
Chet Haased4307532014-12-01 06:32:38 -0800660 boolean gotValues = false;
ztenghuie5e92602014-06-03 14:02:10 -0700661
662 if (name.equals("objectAnimator")) {
Doris Liu7513aab2015-04-18 00:14:14 +0000663 anim = loadObjectAnimator(res, theme, attrs, pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700664 } else if (name.equals("animator")) {
Doris Liu7513aab2015-04-18 00:14:14 +0000665 anim = loadAnimator(res, theme, attrs, null, pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700666 } else if (name.equals("set")) {
667 anim = new AnimatorSet();
668 TypedArray a;
669 if (theme != null) {
ztenghuicf4832f2014-06-17 09:54:45 -0700670 a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
ztenghuie5e92602014-06-03 14:02:10 -0700671 } else {
ztenghuicf4832f2014-06-17 09:54:45 -0700672 a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
ztenghuie5e92602014-06-03 14:02:10 -0700673 }
Yigit Boyard422dc32014-09-25 12:23:35 -0700674 anim.appendChangingConfigurations(a.getChangingConfigurations());
675 int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
George Mountfd3c4742014-09-08 13:23:36 -0700676 createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
Doris Liu7513aab2015-04-18 00:14:14 +0000677 pixelSize);
ztenghuie5e92602014-06-03 14:02:10 -0700678 a.recycle();
Chet Haased4307532014-12-01 06:32:38 -0800679 } else if (name.equals("propertyValuesHolder")) {
680 PropertyValuesHolder[] values = loadValues(res, theme, parser,
681 Xml.asAttributeSet(parser));
682 if (values != null && anim != null && (anim instanceof ValueAnimator)) {
683 ((ValueAnimator) anim).setValues(values);
684 }
685 gotValues = true;
ztenghuie5e92602014-06-03 14:02:10 -0700686 } else {
687 throw new RuntimeException("Unknown animator name: " + parser.getName());
688 }
689
Chet Haased4307532014-12-01 06:32:38 -0800690 if (parent != null && !gotValues) {
ztenghuie5e92602014-06-03 14:02:10 -0700691 if (childAnims == null) {
692 childAnims = new ArrayList<Animator>();
693 }
694 childAnims.add(anim);
695 }
696 }
697 if (parent != null && childAnims != null) {
698 Animator[] animsArray = new Animator[childAnims.size()];
699 int index = 0;
700 for (Animator a : childAnims) {
701 animsArray[index++] = a;
702 }
703 if (sequenceOrdering == TOGETHER) {
704 parent.playTogether(animsArray);
705 } else {
706 parent.playSequentially(animsArray);
707 }
708 }
ztenghuie5e92602014-06-03 14:02:10 -0700709 return anim;
Chet Haased4307532014-12-01 06:32:38 -0800710 }
ztenghuie5e92602014-06-03 14:02:10 -0700711
Chet Haased4307532014-12-01 06:32:38 -0800712 private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
713 XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
714 ArrayList<PropertyValuesHolder> values = null;
715
716 int type;
717 while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
718 type != XmlPullParser.END_DOCUMENT) {
719
720 if (type != XmlPullParser.START_TAG) {
721 parser.next();
722 continue;
723 }
724
725 String name = parser.getName();
726
727 if (name.equals("propertyValuesHolder")) {
728 TypedArray a;
729 if (theme != null) {
730 a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
731 } else {
732 a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
733 }
734 String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
735 int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
Doris Liu9032fa52015-04-09 20:11:22 -0700736 VALUE_TYPE_UNDEFINED);
737
Chet Haased4307532014-12-01 06:32:38 -0800738 PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
739 if (pvh == null) {
740 pvh = getPVH(a, valueType,
741 R.styleable.PropertyValuesHolder_valueFrom,
742 R.styleable.PropertyValuesHolder_valueTo, propertyName);
743 }
744 if (pvh != null) {
745 if (values == null) {
746 values = new ArrayList<PropertyValuesHolder>();
747 }
748 values.add(pvh);
749 }
750 a.recycle();
751 }
752
753 parser.next();
754 }
755
756 PropertyValuesHolder[] valuesArray = null;
757 if (values != null) {
758 int count = values.size();
759 valuesArray = new PropertyValuesHolder[count];
760 for (int i = 0; i < count; ++i) {
761 valuesArray[i] = values.get(i);
762 }
763 }
764 return valuesArray;
765 }
766
Doris Liua01fbf32015-04-21 18:27:26 -0700767 // When no value type is provided in keyframe, we need to infer the type from the value. i.e.
768 // if value is defined in the style of a color value, then the color type is returned.
769 // Otherwise, default float type is returned.
770 private static int inferValueTypeOfKeyframe(Resources res, Theme theme, AttributeSet attrs) {
771 int valueType;
772 TypedArray a;
773 if (theme != null) {
774 a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
775 } else {
776 a = res.obtainAttributes(attrs, R.styleable.Keyframe);
777 }
778
779 TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
780 boolean hasValue = (keyframeValue != null);
781 // When no value type is provided, check whether it's a color type first.
782 // If not, fall back to default value type (i.e. float type).
783 if (hasValue && isColorType(keyframeValue.type)) {
784 valueType = VALUE_TYPE_COLOR;
785 } else {
786 valueType = VALUE_TYPE_FLOAT;
787 }
788 a.recycle();
789 return valueType;
790 }
791
Doris Liu2787ceb2015-06-05 17:03:21 -0700792 private static int inferValueTypeFromValues(TypedArray styledAttributes, int valueFromId,
793 int valueToId) {
794 TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
795 boolean hasFrom = (tvFrom != null);
796 int fromType = hasFrom ? tvFrom.type : 0;
797 TypedValue tvTo = styledAttributes.peekValue(valueToId);
798 boolean hasTo = (tvTo != null);
799 int toType = hasTo ? tvTo.type : 0;
800
801 int valueType;
802 // Check whether it's color type. If not, fall back to default type (i.e. float type)
803 if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
804 valueType = VALUE_TYPE_COLOR;
805 } else {
806 valueType = VALUE_TYPE_FLOAT;
807 }
808 return valueType;
809 }
810
Chet Haased4307532014-12-01 06:32:38 -0800811 private static void dumpKeyframes(Object[] keyframes, String header) {
812 if (keyframes == null || keyframes.length == 0) {
813 return;
814 }
815 Log.d(TAG, header);
816 int count = keyframes.length;
817 for (int i = 0; i < count; ++i) {
818 Keyframe keyframe = (Keyframe) keyframes[i];
819 Log.d(TAG, "Keyframe " + i + ": fraction " +
820 (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " +
821 ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null"));
822 }
823 }
824
Doris Liu9032fa52015-04-09 20:11:22 -0700825 // Load property values holder if there are keyframes defined in it. Otherwise return null.
Chet Haased4307532014-12-01 06:32:38 -0800826 private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
827 String propertyName, int valueType)
828 throws XmlPullParserException, IOException {
829
830 PropertyValuesHolder value = null;
831 ArrayList<Keyframe> keyframes = null;
832
833 int type;
834 while ((type = parser.next()) != XmlPullParser.END_TAG &&
835 type != XmlPullParser.END_DOCUMENT) {
836 String name = parser.getName();
837 if (name.equals("keyframe")) {
Doris Liua01fbf32015-04-21 18:27:26 -0700838 if (valueType == VALUE_TYPE_UNDEFINED) {
839 valueType = inferValueTypeOfKeyframe(res, theme, Xml.asAttributeSet(parser));
840 }
Chet Haased4307532014-12-01 06:32:38 -0800841 Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
842 if (keyframe != null) {
843 if (keyframes == null) {
844 keyframes = new ArrayList<Keyframe>();
845 }
846 keyframes.add(keyframe);
847 }
848 parser.next();
849 }
850 }
851
852 int count;
853 if (keyframes != null && (count = keyframes.size()) > 0) {
854 // make sure we have keyframes at 0 and 1
855 // If we have keyframes with set fractions, add keyframes at start/end
856 // appropriately. If start/end have no set fractions:
857 // if there's only one keyframe, set its fraction to 1 and add one at 0
858 // if >1 keyframe, set the last fraction to 1, the first fraction to 0
859 Keyframe firstKeyframe = keyframes.get(0);
860 Keyframe lastKeyframe = keyframes.get(count - 1);
861 float endFraction = lastKeyframe.getFraction();
862 if (endFraction < 1) {
863 if (endFraction < 0) {
864 lastKeyframe.setFraction(1);
865 } else {
866 keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1));
867 ++count;
868 }
869 }
870 float startFraction = firstKeyframe.getFraction();
871 if (startFraction != 0) {
872 if (startFraction < 0) {
873 firstKeyframe.setFraction(0);
874 } else {
875 keyframes.add(0, createNewKeyframe(firstKeyframe, 0));
876 ++count;
877 }
878 }
879 Keyframe[] keyframeArray = new Keyframe[count];
880 keyframes.toArray(keyframeArray);
881 for (int i = 0; i < count; ++i) {
882 Keyframe keyframe = keyframeArray[i];
883 if (keyframe.getFraction() < 0) {
884 if (i == 0) {
885 keyframe.setFraction(0);
886 } else if (i == count - 1) {
887 keyframe.setFraction(1);
888 } else {
889 // figure out the start/end parameters of the current gap
890 // in fractions and distribute the gap among those keyframes
891 int startIndex = i;
892 int endIndex = i;
893 for (int j = startIndex + 1; j < count - 1; ++j) {
894 if (keyframeArray[j].getFraction() >= 0) {
895 break;
896 }
897 endIndex = j;
898 }
899 float gap = keyframeArray[endIndex + 1].getFraction() -
900 keyframeArray[startIndex - 1].getFraction();
901 distributeKeyframes(keyframeArray, gap, startIndex, endIndex);
902 }
903 }
904 }
905 value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray);
906 if (valueType == VALUE_TYPE_COLOR) {
907 value.setEvaluator(ArgbEvaluator.getInstance());
908 }
909 }
910
911 return value;
912 }
913
914 private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) {
915 return sampleKeyframe.getType() == float.class ?
916 Keyframe.ofFloat(fraction) :
917 (sampleKeyframe.getType() == int.class) ?
918 Keyframe.ofInt(fraction) :
919 Keyframe.ofObject(fraction);
920 }
921
922 /**
923 * Utility function to set fractions on keyframes to cover a gap in which the
924 * fractions are not currently set. Keyframe fractions will be distributed evenly
925 * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap
926 * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the
927 * keyframe before startIndex.
928 * Assumptions:
929 * - First and last keyframe fractions (bounding this spread) are already set. So,
930 * for example, if no fractions are set, we will already set first and last keyframe
931 * fraction values to 0 and 1.
932 * - startIndex must be >0 (which follows from first assumption).
933 * - endIndex must be >= startIndex.
934 *
935 * @param keyframes the array of keyframes
936 * @param gap The total gap we need to distribute
937 * @param startIndex The index of the first keyframe whose fraction must be set
938 * @param endIndex The index of the last keyframe whose fraction must be set
939 */
940 private static void distributeKeyframes(Keyframe[] keyframes, float gap,
941 int startIndex, int endIndex) {
942 int count = endIndex - startIndex + 2;
943 float increment = gap / count;
944 for (int i = startIndex; i <= endIndex; ++i) {
945 keyframes[i].setFraction(keyframes[i-1].getFraction() + increment);
946 }
947 }
948
949 private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
950 int valueType)
951 throws XmlPullParserException, IOException {
952
953 TypedArray a;
954 if (theme != null) {
955 a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
956 } else {
957 a = res.obtainAttributes(attrs, R.styleable.Keyframe);
958 }
959
960 Keyframe keyframe = null;
961
962 float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
963
Doris Liu9032fa52015-04-09 20:11:22 -0700964 TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
965 boolean hasValue = (keyframeValue != null);
966 if (valueType == VALUE_TYPE_UNDEFINED) {
967 // When no value type is provided, check whether it's a color type first.
968 // If not, fall back to default value type (i.e. float type).
969 if (hasValue && isColorType(keyframeValue.type)) {
970 valueType = VALUE_TYPE_COLOR;
971 } else {
972 valueType = VALUE_TYPE_FLOAT;
973 }
974 }
Chet Haased4307532014-12-01 06:32:38 -0800975
976 if (hasValue) {
977 switch (valueType) {
978 case VALUE_TYPE_FLOAT:
979 float value = a.getFloat(R.styleable.Keyframe_value, 0);
980 keyframe = Keyframe.ofFloat(fraction, value);
981 break;
982 case VALUE_TYPE_COLOR:
983 case VALUE_TYPE_INT:
984 int intValue = a.getInt(R.styleable.Keyframe_value, 0);
985 keyframe = Keyframe.ofInt(fraction, intValue);
986 break;
987 }
988 } else {
989 keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
990 Keyframe.ofInt(fraction);
991 }
992
Doris Liu6aac06a2015-04-01 10:27:40 -0700993 final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0);
994 if (resID > 0) {
995 final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
996 keyframe.setInterpolator(interpolator);
997 }
Chet Haased4307532014-12-01 06:32:38 -0800998 a.recycle();
999
1000 return keyframe;
ztenghuie5e92602014-06-03 14:02:10 -07001001 }
1002
George Mountfd3c4742014-09-08 13:23:36 -07001003 private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs,
Doris Liu7513aab2015-04-18 00:14:14 +00001004 float pathErrorScale) throws NotFoundException {
ztenghuie5e92602014-06-03 14:02:10 -07001005 ObjectAnimator anim = new ObjectAnimator();
1006
Doris Liu7513aab2015-04-18 00:14:14 +00001007 loadAnimator(res, theme, attrs, anim, pathErrorScale);
ztenghuie5e92602014-06-03 14:02:10 -07001008
ztenghuie5e92602014-06-03 14:02:10 -07001009 return anim;
1010 }
1011
1012 /**
1013 * Creates a new animation whose parameters come from the specified context
1014 * and attributes set.
1015 *
1016 * @param res The resources
1017 * @param attrs The set of attributes holding the animation parameters
ztenghuicf4832f2014-06-17 09:54:45 -07001018 * @param anim Null if this is a ValueAnimator, otherwise this is an
1019 * ObjectAnimator
ztenghuie5e92602014-06-03 14:02:10 -07001020 */
1021 private static ValueAnimator loadAnimator(Resources res, Theme theme,
Doris Liu7513aab2015-04-18 00:14:14 +00001022 AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
ztenghuie5e92602014-06-03 14:02:10 -07001023 throws NotFoundException {
ztenghuicf4832f2014-06-17 09:54:45 -07001024 TypedArray arrayAnimator = null;
1025 TypedArray arrayObjectAnimator = null;
1026
ztenghuie5e92602014-06-03 14:02:10 -07001027 if (theme != null) {
ztenghuicf4832f2014-06-17 09:54:45 -07001028 arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
ztenghuie5e92602014-06-03 14:02:10 -07001029 } else {
ztenghuicf4832f2014-06-17 09:54:45 -07001030 arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
ztenghuie5e92602014-06-03 14:02:10 -07001031 }
1032
ztenghuicf4832f2014-06-17 09:54:45 -07001033 // If anim is not null, then it is an object animator.
1034 if (anim != null) {
1035 if (theme != null) {
1036 arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
1037 R.styleable.PropertyAnimator, 0, 0);
1038 } else {
1039 arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
1040 }
Yigit Boyard422dc32014-09-25 12:23:35 -07001041 anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
ztenghuicf4832f2014-06-17 09:54:45 -07001042 }
Craig Stout7f9988f2014-08-07 15:25:04 -07001043
1044 if (anim == null) {
1045 anim = new ValueAnimator();
1046 }
Yigit Boyard422dc32014-09-25 12:23:35 -07001047 anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
Craig Stout7f9988f2014-08-07 15:25:04 -07001048
George Mountfd3c4742014-09-08 13:23:36 -07001049 parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
Chet Haased51d3682010-08-11 19:46:48 -07001050
Yigit Boyard422dc32014-09-25 12:23:35 -07001051 final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
Chet Haased51d3682010-08-11 19:46:48 -07001052 if (resID > 0) {
Yigit Boyard422dc32014-09-25 12:23:35 -07001053 final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
1054 if (interpolator instanceof BaseInterpolator) {
1055 anim.appendChangingConfigurations(
1056 ((BaseInterpolator) interpolator).getChangingConfiguration());
1057 }
1058 anim.setInterpolator(interpolator);
Chet Haased51d3682010-08-11 19:46:48 -07001059 }
ztenghuicf4832f2014-06-17 09:54:45 -07001060
1061 arrayAnimator.recycle();
Craig Stout7f9988f2014-08-07 15:25:04 -07001062 if (arrayObjectAnimator != null) {
1063 arrayObjectAnimator.recycle();
1064 }
Chet Haased51d3682010-08-11 19:46:48 -07001065 return anim;
1066 }
Yigit Boyard422dc32014-09-25 12:23:35 -07001067
Alan Viveretteac85f902016-03-11 15:15:51 -05001068 private static @Config int getChangingConfigs(@NonNull Resources resources, @AnyRes int id) {
Yigit Boyard422dc32014-09-25 12:23:35 -07001069 synchronized (sTmpTypedValue) {
1070 resources.getValue(id, sTmpTypedValue, true);
1071 return sTmpTypedValue.changingConfigurations;
1072 }
1073 }
Doris Liu9032fa52015-04-09 20:11:22 -07001074
1075 private static boolean isColorType(int type) {
1076 return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
1077 }
Chet Haased51d3682010-08-11 19:46:48 -07001078}