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 */ 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 */ 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 */ 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 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 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 */ 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 */ 273 public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) { 274 mNodeArray = nodeArray; 275 } 276 277 @Override 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 */ 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 */ 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 */ 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 */ 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 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 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 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 */ 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 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