AnimatorInflater.java revision 83d6e8213230fb0805aa019d266842253baeb114
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_COLOR       = 3;
56    private static final int VALUE_TYPE_CUSTOM      = 4;
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, 0);
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        Object valueFrom = null;
196        Object valueTo = null;
197        TypeEvaluator evaluator = null;
198
199        switch (valueType) {
200            case VALUE_TYPE_FLOAT:
201                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
202                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
203                }
204                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
205                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
206                }
207                break;
208            case VALUE_TYPE_COLOR:
209                evaluator = new RGBEvaluator();
210                // fall through to pick up values
211            case VALUE_TYPE_INT:
212                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
213                    valueFrom = a.getInteger(com.android.internal.R.styleable.Animator_valueFrom, 0);
214                }
215                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
216                    valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0);
217                }
218                break;
219            case VALUE_TYPE_DOUBLE:
220                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
221                    valueFrom = (Double)((Float)(a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f))).doubleValue();
222                }
223                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
224                    valueTo = (Double)((Float)a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f)).doubleValue();
225                }
226                break;
227            case VALUE_TYPE_CUSTOM:
228                // TODO: How to get an 'Object' value?
229                if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) {
230                    valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
231                }
232                if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) {
233                    valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
234                }
235                break;
236        }
237
238        if (anim == null) {
239            anim = new ValueAnimator(duration, valueFrom, valueTo);
240        } else {
241            anim.setDuration(duration);
242            anim.setValues(valueFrom, valueTo);
243        }
244
245        anim.setStartDelay(startDelay);
246
247        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
248            anim.setRepeatCount(
249                    a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
250        }
251        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
252            anim.setRepeatMode(
253                    a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
254                            ValueAnimator.RESTART));
255        }
256        if (evaluator != null) {
257            anim.setEvaluator(evaluator);
258        }
259
260        final int resID =
261                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
262        if (resID > 0) {
263            anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
264        }
265        a.recycle();
266
267        return anim;
268    }
269}
270