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