AnimatorInflater.java revision 2794eb3b02e2404d453d3ad22a8a85a138130a07
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_DOUBLE      = 2;
55    private static final int VALUE_TYPE_LONG        = 3;
56    private static final int VALUE_TYPE_COLOR       = 4;
57    private static final int VALUE_TYPE_CUSTOM      = 5;
58
59    /**
60     * Loads an {@link Animator} object from a resource
61     *
62     * @param context Application context used to access resources
63     * @param id The resource id of the animation to load
64     * @return The animator object reference by the specified id
65     * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
66     */
67    public static Animator loadAnimator(Context context, int id)
68            throws NotFoundException {
69
70        XmlResourceParser parser = null;
71        try {
72            parser = context.getResources().getAnimation(id);
73            return createAnimatorFromXml(context, parser);
74        } catch (XmlPullParserException ex) {
75            Resources.NotFoundException rnf =
76                    new Resources.NotFoundException("Can't load animation resource ID #0x" +
77                    Integer.toHexString(id));
78            rnf.initCause(ex);
79            throw rnf;
80        } catch (IOException ex) {
81            Resources.NotFoundException rnf =
82                    new Resources.NotFoundException("Can't load animation resource ID #0x" +
83                    Integer.toHexString(id));
84            rnf.initCause(ex);
85            throw rnf;
86        } finally {
87            if (parser != null) parser.close();
88        }
89    }
90
91    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
92            throws XmlPullParserException, IOException {
93
94        return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
95    }
96
97    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
98            AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
99            throws XmlPullParserException, IOException {
100
101        Animator anim = null;
102        ArrayList<Animator> childAnims = null;
103
104        // Make sure we are on a start tag.
105        int type;
106        int depth = parser.getDepth();
107
108        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
109               && type != XmlPullParser.END_DOCUMENT) {
110
111            if (type != XmlPullParser.START_TAG) {
112                continue;
113            }
114
115            String  name = parser.getName();
116
117            if (name.equals("objectAnimator")) {
118                anim = loadObjectAnimator(c, attrs);
119            } else if (name.equals("animator")) {
120                anim = loadAnimator(c, attrs, null);
121            } else if (name.equals("set")) {
122                anim = new AnimatorSet();
123                TypedArray a = c.obtainStyledAttributes(attrs,
124                        com.android.internal.R.styleable.AnimatorSet);
125                int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
126                        TOGETHER);
127                createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim,  ordering);
128                a.recycle();
129            } else {
130                throw new RuntimeException("Unknown animator name: " + parser.getName());
131            }
132
133            if (parent != null) {
134                if (childAnims == null) {
135                    childAnims = new ArrayList<Animator>();
136                }
137                childAnims.add(anim);
138            }
139        }
140        if (parent != null && childAnims != null) {
141            Animator[] animsArray = new Animator[childAnims.size()];
142            int index = 0;
143            for (Animator a : childAnims) {
144                animsArray[index++] = a;
145            }
146            if (sequenceOrdering == TOGETHER) {
147                parent.playTogether(animsArray);
148            } else {
149                parent.playSequentially(animsArray);
150            }
151        }
152
153        return anim;
154
155    }
156
157    private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
158            throws NotFoundException {
159
160        ObjectAnimator anim = new ObjectAnimator();
161
162        loadAnimator(context, attrs, anim);
163
164        TypedArray a =
165                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator);
166
167        String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName);
168
169        anim.setPropertyName(propertyName);
170
171        a.recycle();
172
173        return anim;
174    }
175
176    /**
177     * Creates a new animation whose parameters come from the specified context and
178     * attributes set.
179     *
180     * @param context the application environment
181     * @param attrs the set of attributes holding the animation parameters
182     */
183    private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
184            throws NotFoundException {
185
186        TypedArray a =
187                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
188
189        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0);
190
191        long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
192
193        int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
194                VALUE_TYPE_FLOAT);
195
196        if (anim == null) {
197            anim = new ValueAnimator();
198        }
199        TypeEvaluator evaluator = null;
200        boolean hasFrom = a.hasValue(com.android.internal.R.styleable.Animator_valueFrom);
201        boolean hasTo = a.hasValue(com.android.internal.R.styleable.Animator_valueTo);
202
203        switch (valueType) {
204
205            case VALUE_TYPE_FLOAT: {
206                float valueFrom;
207                float valueTo;
208                if (hasFrom) {
209                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
210                    if (hasTo) {
211                        valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
212                        anim.setFloatValues(valueFrom, valueTo);
213                    } else {
214                        anim.setFloatValues(valueFrom);
215                    }
216                } else {
217                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
218                    anim.setFloatValues(valueTo);
219                }
220            }
221            break;
222
223            case VALUE_TYPE_COLOR:
224                evaluator = new RGBEvaluator();
225                anim.setEvaluator(evaluator);
226                // fall through to pick up values
227            case VALUE_TYPE_INT: {
228                int valueFrom;
229                int valueTo;
230                if (hasFrom) {
231                    valueFrom = a.getInteger(com.android.internal.R.styleable.Animator_valueFrom, 0);
232                    if (hasTo) {
233                        valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0);
234                        anim.setIntValues(valueFrom, valueTo);
235                    } else {
236                        anim.setIntValues(valueFrom);
237                    }
238                } else {
239                    valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0);
240                    anim.setIntValues(valueTo);
241                }
242            }
243            break;
244
245            case VALUE_TYPE_LONG: {
246                int valueFrom;
247                int valueTo;
248                if (hasFrom) {
249                    valueFrom = a.getInteger(com.android.internal.R.styleable.Animator_valueFrom, 0);
250                    if (hasTo) {
251                        valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0);
252                        anim.setLongValues(valueFrom, valueTo);
253                    } else {
254                        anim.setLongValues(valueFrom);
255                    }
256                } else {
257                    valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0);
258                    anim.setLongValues(valueTo);
259                }
260            }
261            break;
262
263            case VALUE_TYPE_DOUBLE: {
264                double valueFrom;
265                double valueTo;
266                if (hasFrom) {
267                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
268                    if (hasTo) {
269                        valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
270                        anim.setDoubleValues(valueFrom, valueTo);
271                    } else {
272                        anim.setDoubleValues(valueFrom);
273                    }
274                } else {
275                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
276                    anim.setDoubleValues(valueTo);
277                }
278            }
279            break;
280
281            case VALUE_TYPE_CUSTOM: {
282                // TODO: How to get an 'Object' value?
283                float valueFrom;
284                float valueTo;
285                if (hasFrom) {
286                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
287                    if (hasTo) {
288                        valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
289                        anim.setFloatValues(valueFrom, valueTo);
290                    } else {
291                        anim.setFloatValues(valueFrom);
292                    }
293                } else {
294                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
295                    anim.setFloatValues(valueTo);
296                }
297            }
298            break;
299        }
300
301
302        anim.setDuration(duration);
303        anim.setStartDelay(startDelay);
304
305        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
306            anim.setRepeatCount(
307                    a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
308        }
309        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
310            anim.setRepeatMode(
311                    a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
312                            ValueAnimator.RESTART));
313        }
314        if (evaluator != null) {
315            anim.setEvaluator(evaluator);
316        }
317
318        final int resID =
319                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
320        if (resID > 0) {
321            anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
322        }
323        a.recycle();
324
325        return anim;
326    }
327}
328