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.Resources;
     20 import android.content.res.TypedArray;
     21 import android.content.res.XmlResourceParser;
     22 import android.content.res.Resources.NotFoundException;
     23 import android.util.AttributeSet;
     24 import android.util.TypedValue;
     25 import android.util.Xml;
     26 import android.view.animation.AnimationUtils;
     27 import org.xmlpull.v1.XmlPullParser;
     28 import org.xmlpull.v1.XmlPullParserException;
     29 
     30 import java.io.IOException;
     31 import java.util.ArrayList;
     32 
     33 /**
     34  * This class is used to instantiate animator XML files into Animator objects.
     35  * <p>
     36  * For performance reasons, inflation relies heavily on pre-processing of
     37  * XML files that is done at build time. Therefore, it is not currently possible
     38  * to use this inflater with an XmlPullParser over a plain XML file at runtime;
     39  * it only works with an XmlPullParser returned from a compiled resource (R.
     40  * <em>something</em> file.)
     41  */
     42 public class AnimatorInflater {
     43 
     44     /**
     45      * These flags are used when parsing AnimatorSet objects
     46      */
     47     private static final int TOGETHER = 0;
     48     private static final int SEQUENTIALLY = 1;
     49 
     50     /**
     51      * Enum values used in XML attributes to indicate the value for mValueType
     52      */
     53     private static final int VALUE_TYPE_FLOAT       = 0;
     54     private static final int VALUE_TYPE_INT         = 1;
     55     private static final int VALUE_TYPE_COLOR       = 4;
     56     private static final int VALUE_TYPE_CUSTOM      = 5;
     57 
     58     /**
     59      * Loads an {@link Animator} object from a resource
     60      *
     61      * @param context Application context used to access resources
     62      * @param id The resource id of the animation to load
     63      * @return The animator object reference by the specified id
     64      * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
     65      */
     66     public static Animator loadAnimator(Context context, int id)
     67             throws NotFoundException {
     68 
     69         XmlResourceParser parser = null;
     70         try {
     71             parser = context.getResources().getAnimation(id);
     72             return createAnimatorFromXml(context, parser);
     73         } catch (XmlPullParserException ex) {
     74             Resources.NotFoundException rnf =
     75                     new Resources.NotFoundException("Can't load animation resource ID #0x" +
     76                     Integer.toHexString(id));
     77             rnf.initCause(ex);
     78             throw rnf;
     79         } catch (IOException ex) {
     80             Resources.NotFoundException rnf =
     81                     new Resources.NotFoundException("Can't load animation resource ID #0x" +
     82                     Integer.toHexString(id));
     83             rnf.initCause(ex);
     84             throw rnf;
     85         } finally {
     86             if (parser != null) parser.close();
     87         }
     88     }
     89 
     90     private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
     91             throws XmlPullParserException, IOException {
     92 
     93         return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
     94     }
     95 
     96     private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
     97             AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
     98             throws XmlPullParserException, IOException {
     99 
    100         Animator anim = null;
    101         ArrayList<Animator> childAnims = null;
    102 
    103         // Make sure we are on a start tag.
    104         int type;
    105         int depth = parser.getDepth();
    106 
    107         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
    108                && type != XmlPullParser.END_DOCUMENT) {
    109 
    110             if (type != XmlPullParser.START_TAG) {
    111                 continue;
    112             }
    113 
    114             String  name = parser.getName();
    115 
    116             if (name.equals("objectAnimator")) {
    117                 anim = loadObjectAnimator(c, attrs);
    118             } else if (name.equals("animator")) {
    119                 anim = loadAnimator(c, attrs, null);
    120             } else if (name.equals("set")) {
    121                 anim = new AnimatorSet();
    122                 TypedArray a = c.obtainStyledAttributes(attrs,
    123                         com.android.internal.R.styleable.AnimatorSet);
    124                 int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
    125                         TOGETHER);
    126                 createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim,  ordering);
    127                 a.recycle();
    128             } else {
    129                 throw new RuntimeException("Unknown animator name: " + parser.getName());
    130             }
    131 
    132             if (parent != null) {
    133                 if (childAnims == null) {
    134                     childAnims = new ArrayList<Animator>();
    135                 }
    136                 childAnims.add(anim);
    137             }
    138         }
    139         if (parent != null && childAnims != null) {
    140             Animator[] animsArray = new Animator[childAnims.size()];
    141             int index = 0;
    142             for (Animator a : childAnims) {
    143                 animsArray[index++] = a;
    144             }
    145             if (sequenceOrdering == TOGETHER) {
    146                 parent.playTogether(animsArray);
    147             } else {
    148                 parent.playSequentially(animsArray);
    149             }
    150         }
    151 
    152         return anim;
    153 
    154     }
    155 
    156     private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
    157             throws NotFoundException {
    158 
    159         ObjectAnimator anim = new ObjectAnimator();
    160 
    161         loadAnimator(context, attrs, anim);
    162 
    163         TypedArray a =
    164                 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator);
    165 
    166         String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName);
    167 
    168         anim.setPropertyName(propertyName);
    169 
    170         a.recycle();
    171 
    172         return anim;
    173     }
    174 
    175     /**
    176      * Creates a new animation whose parameters come from the specified context and
    177      * attributes set.
    178      *
    179      * @param context the application environment
    180      * @param attrs the set of attributes holding the animation parameters
    181      */
    182     private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
    183             throws NotFoundException {
    184 
    185         TypedArray a =
    186                 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
    187 
    188         long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
    189 
    190         long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
    191 
    192         int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
    193                 VALUE_TYPE_FLOAT);
    194 
    195         if (anim == null) {
    196             anim = new ValueAnimator();
    197         }
    198         TypeEvaluator evaluator = null;
    199 
    200         int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom;
    201         int valueToIndex = com.android.internal.R.styleable.Animator_valueTo;
    202 
    203         boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
    204 
    205         TypedValue tvFrom = a.peekValue(valueFromIndex);
    206         boolean hasFrom = (tvFrom != null);
    207         int fromType = hasFrom ? tvFrom.type : 0;
    208         TypedValue tvTo = a.peekValue(valueToIndex);
    209         boolean hasTo = (tvTo != null);
    210         int toType = hasTo ? tvTo.type : 0;
    211 
    212         if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
    213                 (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
    214             (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
    215                 (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
    216             // special case for colors: ignore valueType and get ints
    217             getFloats = false;
    218             anim.setEvaluator(new ArgbEvaluator());
    219         }
    220 
    221         if (getFloats) {
    222             float valueFrom;
    223             float valueTo;
    224             if (hasFrom) {
    225                 if (fromType == TypedValue.TYPE_DIMENSION) {
    226                     valueFrom = a.getDimension(valueFromIndex, 0f);
    227                 } else {
    228                     valueFrom = a.getFloat(valueFromIndex, 0f);
    229                 }
    230                 if (hasTo) {
    231                     if (toType == TypedValue.TYPE_DIMENSION) {
    232                         valueTo = a.getDimension(valueToIndex, 0f);
    233                     } else {
    234                         valueTo = a.getFloat(valueToIndex, 0f);
    235                     }
    236                     anim.setFloatValues(valueFrom, valueTo);
    237                 } else {
    238                     anim.setFloatValues(valueFrom);
    239                 }
    240             } else {
    241                 if (toType == TypedValue.TYPE_DIMENSION) {
    242                     valueTo = a.getDimension(valueToIndex, 0f);
    243                 } else {
    244                     valueTo = a.getFloat(valueToIndex, 0f);
    245                 }
    246                 anim.setFloatValues(valueTo);
    247             }
    248         } else {
    249             int valueFrom;
    250             int valueTo;
    251             if (hasFrom) {
    252                 if (fromType == TypedValue.TYPE_DIMENSION) {
    253                     valueFrom = (int) a.getDimension(valueFromIndex, 0f);
    254                 } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
    255                         (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
    256                     valueFrom = a.getColor(valueFromIndex, 0);
    257                 } else {
    258                     valueFrom = a.getInt(valueFromIndex, 0);
    259                 }
    260                 if (hasTo) {
    261                     if (toType == TypedValue.TYPE_DIMENSION) {
    262                         valueTo = (int) a.getDimension(valueToIndex, 0f);
    263                     } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
    264                             (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
    265                         valueTo = a.getColor(valueToIndex, 0);
    266                     } else {
    267                         valueTo = a.getInt(valueToIndex, 0);
    268                     }
    269                     anim.setIntValues(valueFrom, valueTo);
    270                 } else {
    271                     anim.setIntValues(valueFrom);
    272                 }
    273             } else {
    274                 if (hasTo) {
    275                     if (toType == TypedValue.TYPE_DIMENSION) {
    276                         valueTo = (int) a.getDimension(valueToIndex, 0f);
    277                     } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
    278                         (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
    279                         valueTo = a.getColor(valueToIndex, 0);
    280                     } else {
    281                         valueTo = a.getInt(valueToIndex, 0);
    282                     }
    283                     anim.setIntValues(valueTo);
    284                 }
    285             }
    286         }
    287 
    288         anim.setDuration(duration);
    289         anim.setStartDelay(startDelay);
    290 
    291         if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
    292             anim.setRepeatCount(
    293                     a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
    294         }
    295         if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
    296             anim.setRepeatMode(
    297                     a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
    298                             ValueAnimator.RESTART));
    299         }
    300         if (evaluator != null) {
    301             anim.setEvaluator(evaluator);
    302         }
    303 
    304         final int resID =
    305                 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
    306         if (resID > 0) {
    307             anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
    308         }
    309         a.recycle();
    310 
    311         return anim;
    312     }
    313 }
    314