Home | History | Annotate | Download | only in animation
      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