1 /* 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 */ 16 package android.animation; 17 18 import android.content.Context; 19 import android.content.res.ConfigurationBoundResourceCache; 20 import android.content.res.ConstantState; 21 import android.content.res.Resources; 22 import android.content.res.Resources.NotFoundException; 23 import android.content.res.Resources.Theme; 24 import android.content.res.TypedArray; 25 import android.content.res.XmlResourceParser; 26 import android.graphics.Path; 27 import android.util.AttributeSet; 28 import android.util.Log; 29 import android.util.PathParser; 30 import android.util.StateSet; 31 import android.util.TypedValue; 32 import android.util.Xml; 33 import android.view.InflateException; 34 import android.view.animation.AnimationUtils; 35 import android.view.animation.BaseInterpolator; 36 import android.view.animation.Interpolator; 37 38 import com.android.internal.R; 39 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 43 import java.io.IOException; 44 import java.util.ArrayList; 45 46 /** 47 * This class is used to instantiate animator XML files into Animator objects. 48 * <p> 49 * For performance reasons, inflation relies heavily on pre-processing of 50 * XML files that is done at build time. Therefore, it is not currently possible 51 * to use this inflater with an XmlPullParser over a plain XML file at runtime; 52 * it only works with an XmlPullParser returned from a compiled resource (R. 53 * <em>something</em> file.) 54 */ 55 public class AnimatorInflater { 56 private static final String TAG = "AnimatorInflater"; 57 /** 58 * These flags are used when parsing AnimatorSet objects 59 */ 60 private static final int TOGETHER = 0; 61 private static final int SEQUENTIALLY = 1; 62 63 /** 64 * Enum values used in XML attributes to indicate the value for mValueType 65 */ 66 private static final int VALUE_TYPE_FLOAT = 0; 67 private static final int VALUE_TYPE_INT = 1; 68 private static final int VALUE_TYPE_PATH = 2; 69 private static final int VALUE_TYPE_COLOR = 4; 70 private static final int VALUE_TYPE_CUSTOM = 5; 71 72 private static final boolean DBG_ANIMATOR_INFLATER = false; 73 74 // used to calculate changing configs for resource references 75 private static final TypedValue sTmpTypedValue = new TypedValue(); 76 77 /** 78 * Loads an {@link Animator} object from a resource 79 * 80 * @param context Application context used to access resources 81 * @param id The resource id of the animation to load 82 * @return The animator object reference by the specified id 83 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded 84 */ loadAnimator(Context context, int id)85 public static Animator loadAnimator(Context context, int id) 86 throws NotFoundException { 87 return loadAnimator(context.getResources(), context.getTheme(), id); 88 } 89 90 /** 91 * Loads an {@link Animator} object from a resource 92 * 93 * @param resources The resources 94 * @param theme The theme 95 * @param id The resource id of the animation to load 96 * @return The animator object reference by the specified id 97 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded 98 * @hide 99 */ loadAnimator(Resources resources, Theme theme, int id)100 public static Animator loadAnimator(Resources resources, Theme theme, int id) 101 throws NotFoundException { 102 return loadAnimator(resources, theme, id, 1); 103 } 104 105 /** @hide */ loadAnimator(Resources resources, Theme theme, int id, float pathErrorScale)106 public static Animator loadAnimator(Resources resources, Theme theme, int id, 107 float pathErrorScale) throws NotFoundException { 108 final ConfigurationBoundResourceCache<Animator> animatorCache = resources 109 .getAnimatorCache(); 110 Animator animator = animatorCache.get(id, theme); 111 if (animator != null) { 112 if (DBG_ANIMATOR_INFLATER) { 113 Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id)); 114 } 115 return animator; 116 } else if (DBG_ANIMATOR_INFLATER) { 117 Log.d(TAG, "cache miss for animator " + resources.getResourceName(id)); 118 } 119 XmlResourceParser parser = null; 120 try { 121 parser = resources.getAnimation(id); 122 animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale); 123 if (animator != null) { 124 animator.appendChangingConfigurations(getChangingConfigs(resources, id)); 125 final ConstantState<Animator> constantState = animator.createConstantState(); 126 if (constantState != null) { 127 if (DBG_ANIMATOR_INFLATER) { 128 Log.d(TAG, "caching animator for res " + resources.getResourceName(id)); 129 } 130 animatorCache.put(id, theme, constantState); 131 // create a new animator so that cached version is never used by the user 132 animator = constantState.newInstance(resources, theme); 133 } 134 } 135 return animator; 136 } catch (XmlPullParserException ex) { 137 Resources.NotFoundException rnf = 138 new Resources.NotFoundException("Can't load animation resource ID #0x" + 139 Integer.toHexString(id)); 140 rnf.initCause(ex); 141 throw rnf; 142 } catch (IOException ex) { 143 Resources.NotFoundException rnf = 144 new Resources.NotFoundException("Can't load animation resource ID #0x" + 145 Integer.toHexString(id)); 146 rnf.initCause(ex); 147 throw rnf; 148 } finally { 149 if (parser != null) parser.close(); 150 } 151 } 152 loadStateListAnimator(Context context, int id)153 public static StateListAnimator loadStateListAnimator(Context context, int id) 154 throws NotFoundException { 155 final Resources resources = context.getResources(); 156 final ConfigurationBoundResourceCache<StateListAnimator> cache = resources 157 .getStateListAnimatorCache(); 158 final Theme theme = context.getTheme(); 159 StateListAnimator animator = cache.get(id, theme); 160 if (animator != null) { 161 return animator; 162 } 163 XmlResourceParser parser = null; 164 try { 165 parser = resources.getAnimation(id); 166 animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser)); 167 if (animator != null) { 168 animator.appendChangingConfigurations(getChangingConfigs(resources, id)); 169 final ConstantState<StateListAnimator> constantState = animator 170 .createConstantState(); 171 if (constantState != null) { 172 cache.put(id, theme, constantState); 173 // return a clone so that the animator in constant state is never used. 174 animator = constantState.newInstance(resources, theme); 175 } 176 } 177 return animator; 178 } catch (XmlPullParserException ex) { 179 Resources.NotFoundException rnf = 180 new Resources.NotFoundException( 181 "Can't load state list animator resource ID #0x" + 182 Integer.toHexString(id) 183 ); 184 rnf.initCause(ex); 185 throw rnf; 186 } catch (IOException ex) { 187 Resources.NotFoundException rnf = 188 new Resources.NotFoundException( 189 "Can't load state list animator resource ID #0x" + 190 Integer.toHexString(id) 191 ); 192 rnf.initCause(ex); 193 throw rnf; 194 } finally { 195 if (parser != null) { 196 parser.close(); 197 } 198 } 199 } 200 createStateListAnimatorFromXml(Context context, XmlPullParser parser, AttributeSet attributeSet)201 private static StateListAnimator createStateListAnimatorFromXml(Context context, 202 XmlPullParser parser, AttributeSet attributeSet) 203 throws IOException, XmlPullParserException { 204 int type; 205 StateListAnimator stateListAnimator = new StateListAnimator(); 206 207 while (true) { 208 type = parser.next(); 209 switch (type) { 210 case XmlPullParser.END_DOCUMENT: 211 case XmlPullParser.END_TAG: 212 return stateListAnimator; 213 214 case XmlPullParser.START_TAG: 215 // parse item 216 Animator animator = null; 217 if ("item".equals(parser.getName())) { 218 int attributeCount = parser.getAttributeCount(); 219 int[] states = new int[attributeCount]; 220 int stateIndex = 0; 221 for (int i = 0; i < attributeCount; i++) { 222 int attrName = attributeSet.getAttributeNameResource(i); 223 if (attrName == R.attr.animation) { 224 final int animId = attributeSet.getAttributeResourceValue(i, 0); 225 animator = loadAnimator(context, animId); 226 } else { 227 states[stateIndex++] = 228 attributeSet.getAttributeBooleanValue(i, false) ? 229 attrName : -attrName; 230 } 231 } 232 if (animator == null) { 233 animator = createAnimatorFromXml(context.getResources(), 234 context.getTheme(), parser, 1f); 235 } 236 237 if (animator == null) { 238 throw new Resources.NotFoundException( 239 "animation state item must have a valid animation"); 240 } 241 stateListAnimator 242 .addState(StateSet.trimStateSet(states, stateIndex), animator); 243 } 244 break; 245 } 246 } 247 } 248 249 /** 250 * PathDataEvaluator is used to interpolate between two paths which are 251 * represented in the same format but different control points' values. 252 * The path is represented as an array of PathDataNode here, which is 253 * fundamentally an array of floating point numbers. 254 */ 255 private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> { 256 private PathParser.PathDataNode[] mNodeArray; 257 258 /** 259 * Create a PathParser.PathDataNode[] that does not reuse the animated value. 260 * Care must be taken when using this option because on every evaluation 261 * a new <code>PathParser.PathDataNode[]</code> will be allocated. 262 */ PathDataEvaluator()263 private PathDataEvaluator() {} 264 265 /** 266 * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call. 267 * Caution must be taken to ensure that the value returned from 268 * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or 269 * used across threads. The value will be modified on each <code>evaluate()</code> call. 270 * 271 * @param nodeArray The array to modify and return from <code>evaluate</code>. 272 */ PathDataEvaluator(PathParser.PathDataNode[] nodeArray)273 public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) { 274 mNodeArray = nodeArray; 275 } 276 277 @Override evaluate(float fraction, PathParser.PathDataNode[] startPathData, PathParser.PathDataNode[] endPathData)278 public PathParser.PathDataNode[] evaluate(float fraction, 279 PathParser.PathDataNode[] startPathData, 280 PathParser.PathDataNode[] endPathData) { 281 if (!PathParser.canMorph(startPathData, endPathData)) { 282 throw new IllegalArgumentException("Can't interpolate between" 283 + " two incompatible pathData"); 284 } 285 286 if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) { 287 mNodeArray = PathParser.deepCopyNodes(startPathData); 288 } 289 290 for (int i = 0; i < startPathData.length; i++) { 291 mNodeArray[i].interpolatePathDataNode(startPathData[i], 292 endPathData[i], fraction); 293 } 294 295 return mNodeArray; 296 } 297 } 298 299 /** 300 * @param anim The animator, must not be null 301 * @param arrayAnimator Incoming typed array for Animator's attributes. 302 * @param arrayObjectAnimator Incoming typed array for Object Animator's 303 * attributes. 304 * @param pixelSize The relative pixel size, used to calculate the 305 * maximum error for path animations. 306 */ parseAnimatorFromTypeArray(ValueAnimator anim, TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize)307 private static void parseAnimatorFromTypeArray(ValueAnimator anim, 308 TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) { 309 long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300); 310 311 long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0); 312 313 int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, 314 VALUE_TYPE_FLOAT); 315 316 TypeEvaluator evaluator = null; 317 318 boolean getFloats = (valueType == VALUE_TYPE_FLOAT); 319 320 TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom); 321 boolean hasFrom = (tvFrom != null); 322 int fromType = hasFrom ? tvFrom.type : 0; 323 TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo); 324 boolean hasTo = (tvTo != null); 325 int toType = hasTo ? tvTo.type : 0; 326 327 // TODO: Further clean up this part of code into 4 types : path, color, 328 // integer and float. 329 if (valueType == VALUE_TYPE_PATH) { 330 evaluator = setupAnimatorForPath(anim, arrayAnimator); 331 } else { 332 // Integer and float value types are handled here. 333 if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && 334 (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) || 335 (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) && 336 (toType <= TypedValue.TYPE_LAST_COLOR_INT))) { 337 // special case for colors: ignore valueType and get ints 338 getFloats = false; 339 evaluator = ArgbEvaluator.getInstance(); 340 } 341 setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType); 342 } 343 344 anim.setDuration(duration); 345 anim.setStartDelay(startDelay); 346 347 if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) { 348 anim.setRepeatCount( 349 arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0)); 350 } 351 if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) { 352 anim.setRepeatMode( 353 arrayAnimator.getInt(R.styleable.Animator_repeatMode, 354 ValueAnimator.RESTART)); 355 } 356 if (evaluator != null) { 357 anim.setEvaluator(evaluator); 358 } 359 360 if (arrayObjectAnimator != null) { 361 setupObjectAnimator(anim, arrayObjectAnimator, getFloats, pixelSize); 362 } 363 } 364 365 /** 366 * Setup the Animator to achieve path morphing. 367 * 368 * @param anim The target Animator which will be updated. 369 * @param arrayAnimator TypedArray for the ValueAnimator. 370 * @return the PathDataEvaluator. 371 */ setupAnimatorForPath(ValueAnimator anim, TypedArray arrayAnimator)372 private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim, 373 TypedArray arrayAnimator) { 374 TypeEvaluator evaluator = null; 375 String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom); 376 String toString = arrayAnimator.getString(R.styleable.Animator_valueTo); 377 PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); 378 PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); 379 380 if (nodesFrom != null) { 381 if (nodesTo != null) { 382 anim.setObjectValues(nodesFrom, nodesTo); 383 if (!PathParser.canMorph(nodesFrom, nodesTo)) { 384 throw new InflateException(arrayAnimator.getPositionDescription() 385 + " Can't morph from " + fromString + " to " + toString); 386 } 387 } else { 388 anim.setObjectValues((Object)nodesFrom); 389 } 390 evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); 391 } else if (nodesTo != null) { 392 anim.setObjectValues((Object)nodesTo); 393 evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); 394 } 395 396 if (DBG_ANIMATOR_INFLATER && evaluator != null) { 397 Log.v(TAG, "create a new PathDataEvaluator here"); 398 } 399 400 return evaluator; 401 } 402 403 /** 404 * Setup ObjectAnimator's property or values from pathData. 405 * 406 * @param anim The target Animator which will be updated. 407 * @param arrayObjectAnimator TypedArray for the ObjectAnimator. 408 * @param getFloats True if the value type is float. 409 * @param pixelSize The relative pixel size, used to calculate the 410 * maximum error for path animations. 411 */ setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator, boolean getFloats, float pixelSize)412 private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator, 413 boolean getFloats, float pixelSize) { 414 ObjectAnimator oa = (ObjectAnimator) anim; 415 String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData); 416 417 // Note that if there is a pathData defined in the Object Animator, 418 // valueFrom / valueTo will be ignored. 419 if (pathData != null) { 420 String propertyXName = 421 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName); 422 String propertyYName = 423 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName); 424 425 if (propertyXName == null && propertyYName == null) { 426 throw new InflateException(arrayObjectAnimator.getPositionDescription() 427 + " propertyXName or propertyYName is needed for PathData"); 428 } else { 429 Path path = PathParser.createPathFromPathData(pathData); 430 float error = 0.5f * pixelSize; // max half a pixel error 431 PathKeyframes keyframeSet = KeyframeSet.ofPath(path, error); 432 Keyframes xKeyframes; 433 Keyframes yKeyframes; 434 if (getFloats) { 435 xKeyframes = keyframeSet.createXFloatKeyframes(); 436 yKeyframes = keyframeSet.createYFloatKeyframes(); 437 } else { 438 xKeyframes = keyframeSet.createXIntKeyframes(); 439 yKeyframes = keyframeSet.createYIntKeyframes(); 440 } 441 PropertyValuesHolder x = null; 442 PropertyValuesHolder y = null; 443 if (propertyXName != null) { 444 x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes); 445 } 446 if (propertyYName != null) { 447 y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes); 448 } 449 if (x == null) { 450 oa.setValues(y); 451 } else if (y == null) { 452 oa.setValues(x); 453 } else { 454 oa.setValues(x, y); 455 } 456 } 457 } else { 458 String propertyName = 459 arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName); 460 oa.setPropertyName(propertyName); 461 } 462 } 463 464 /** 465 * Setup ValueAnimator's values. 466 * This will handle all of the integer, float and color types. 467 * 468 * @param anim The target Animator which will be updated. 469 * @param arrayAnimator TypedArray for the ValueAnimator. 470 * @param getFloats True if the value type is float. 471 * @param hasFrom True if "valueFrom" exists. 472 * @param fromType The type of "valueFrom". 473 * @param hasTo True if "valueTo" exists. 474 * @param toType The type of "valueTo". 475 */ setupValues(ValueAnimator anim, TypedArray arrayAnimator, boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType)476 private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator, 477 boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) { 478 int valueFromIndex = R.styleable.Animator_valueFrom; 479 int valueToIndex = R.styleable.Animator_valueTo; 480 if (getFloats) { 481 float valueFrom; 482 float valueTo; 483 if (hasFrom) { 484 if (fromType == TypedValue.TYPE_DIMENSION) { 485 valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f); 486 } else { 487 valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f); 488 } 489 if (hasTo) { 490 if (toType == TypedValue.TYPE_DIMENSION) { 491 valueTo = arrayAnimator.getDimension(valueToIndex, 0f); 492 } else { 493 valueTo = arrayAnimator.getFloat(valueToIndex, 0f); 494 } 495 anim.setFloatValues(valueFrom, valueTo); 496 } else { 497 anim.setFloatValues(valueFrom); 498 } 499 } else { 500 if (toType == TypedValue.TYPE_DIMENSION) { 501 valueTo = arrayAnimator.getDimension(valueToIndex, 0f); 502 } else { 503 valueTo = arrayAnimator.getFloat(valueToIndex, 0f); 504 } 505 anim.setFloatValues(valueTo); 506 } 507 } else { 508 int valueFrom; 509 int valueTo; 510 if (hasFrom) { 511 if (fromType == TypedValue.TYPE_DIMENSION) { 512 valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f); 513 } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && 514 (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) { 515 valueFrom = arrayAnimator.getColor(valueFromIndex, 0); 516 } else { 517 valueFrom = arrayAnimator.getInt(valueFromIndex, 0); 518 } 519 if (hasTo) { 520 if (toType == TypedValue.TYPE_DIMENSION) { 521 valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f); 522 } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && 523 (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { 524 valueTo = arrayAnimator.getColor(valueToIndex, 0); 525 } else { 526 valueTo = arrayAnimator.getInt(valueToIndex, 0); 527 } 528 anim.setIntValues(valueFrom, valueTo); 529 } else { 530 anim.setIntValues(valueFrom); 531 } 532 } else { 533 if (hasTo) { 534 if (toType == TypedValue.TYPE_DIMENSION) { 535 valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f); 536 } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && 537 (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { 538 valueTo = arrayAnimator.getColor(valueToIndex, 0); 539 } else { 540 valueTo = arrayAnimator.getInt(valueToIndex, 0); 541 } 542 anim.setIntValues(valueTo); 543 } 544 } 545 } 546 } 547 createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser, float pixelSize)548 private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser, 549 float pixelSize) 550 throws XmlPullParserException, IOException { 551 return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0, 552 pixelSize); 553 } 554 createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser, AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)555 private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser, 556 AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize) 557 throws XmlPullParserException, IOException { 558 Animator anim = null; 559 ArrayList<Animator> childAnims = null; 560 561 // Make sure we are on a start tag. 562 int type; 563 int depth = parser.getDepth(); 564 565 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 566 && type != XmlPullParser.END_DOCUMENT) { 567 568 if (type != XmlPullParser.START_TAG) { 569 continue; 570 } 571 572 String name = parser.getName(); 573 574 if (name.equals("objectAnimator")) { 575 anim = loadObjectAnimator(res, theme, attrs, pixelSize); 576 } else if (name.equals("animator")) { 577 anim = loadAnimator(res, theme, attrs, null, pixelSize); 578 } else if (name.equals("set")) { 579 anim = new AnimatorSet(); 580 TypedArray a; 581 if (theme != null) { 582 a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0); 583 } else { 584 a = res.obtainAttributes(attrs, R.styleable.AnimatorSet); 585 } 586 anim.appendChangingConfigurations(a.getChangingConfigurations()); 587 int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER); 588 createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering, 589 pixelSize); 590 a.recycle(); 591 } else { 592 throw new RuntimeException("Unknown animator name: " + parser.getName()); 593 } 594 595 if (parent != null) { 596 if (childAnims == null) { 597 childAnims = new ArrayList<Animator>(); 598 } 599 childAnims.add(anim); 600 } 601 } 602 if (parent != null && childAnims != null) { 603 Animator[] animsArray = new Animator[childAnims.size()]; 604 int index = 0; 605 for (Animator a : childAnims) { 606 animsArray[index++] = a; 607 } 608 if (sequenceOrdering == TOGETHER) { 609 parent.playTogether(animsArray); 610 } else { 611 parent.playSequentially(animsArray); 612 } 613 } 614 return anim; 615 616 } 617 loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs, float pathErrorScale)618 private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs, 619 float pathErrorScale) throws NotFoundException { 620 ObjectAnimator anim = new ObjectAnimator(); 621 622 loadAnimator(res, theme, attrs, anim, pathErrorScale); 623 624 return anim; 625 } 626 627 /** 628 * Creates a new animation whose parameters come from the specified context 629 * and attributes set. 630 * 631 * @param res The resources 632 * @param attrs The set of attributes holding the animation parameters 633 * @param anim Null if this is a ValueAnimator, otherwise this is an 634 * ObjectAnimator 635 */ loadAnimator(Resources res, Theme theme, AttributeSet attrs, ValueAnimator anim, float pathErrorScale)636 private static ValueAnimator loadAnimator(Resources res, Theme theme, 637 AttributeSet attrs, ValueAnimator anim, float pathErrorScale) 638 throws NotFoundException { 639 TypedArray arrayAnimator = null; 640 TypedArray arrayObjectAnimator = null; 641 642 if (theme != null) { 643 arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0); 644 } else { 645 arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator); 646 } 647 648 // If anim is not null, then it is an object animator. 649 if (anim != null) { 650 if (theme != null) { 651 arrayObjectAnimator = theme.obtainStyledAttributes(attrs, 652 R.styleable.PropertyAnimator, 0, 0); 653 } else { 654 arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator); 655 } 656 anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations()); 657 } 658 659 if (anim == null) { 660 anim = new ValueAnimator(); 661 } 662 anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations()); 663 664 parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale); 665 666 final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0); 667 if (resID > 0) { 668 final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID); 669 if (interpolator instanceof BaseInterpolator) { 670 anim.appendChangingConfigurations( 671 ((BaseInterpolator) interpolator).getChangingConfiguration()); 672 } 673 anim.setInterpolator(interpolator); 674 } 675 676 arrayAnimator.recycle(); 677 if (arrayObjectAnimator != null) { 678 arrayObjectAnimator.recycle(); 679 } 680 return anim; 681 } 682 getChangingConfigs(Resources resources, int id)683 private static int getChangingConfigs(Resources resources, int id) { 684 synchronized (sTmpTypedValue) { 685 resources.getValue(id, sTmpTypedValue, true); 686 return sTmpTypedValue.changingConfigurations; 687 } 688 } 689 } 690