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.TypedValue;
25import android.util.Xml;
26import android.view.animation.AnimationUtils;
27import org.xmlpull.v1.XmlPullParser;
28import org.xmlpull.v1.XmlPullParserException;
29
30import java.io.IOException;
31import 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 */
42public 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, 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        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