1/*
2 * Copyright (C) 2007 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
17package android.view.animation;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.annotation.AnimRes;
23import android.annotation.InterpolatorRes;
24import android.content.Context;
25import android.content.res.Resources;
26import android.content.res.Resources.Theme;
27import android.content.res.XmlResourceParser;
28import android.content.res.Resources.NotFoundException;
29import android.util.AttributeSet;
30import android.util.Xml;
31import android.os.SystemClock;
32
33import java.io.IOException;
34
35/**
36 * Defines common utilities for working with animations.
37 *
38 */
39public class AnimationUtils {
40
41    /**
42     * These flags are used when parsing AnimatorSet objects
43     */
44    private static final int TOGETHER = 0;
45    private static final int SEQUENTIALLY = 1;
46
47
48    /**
49     * Returns the current animation time in milliseconds. This time should be used when invoking
50     * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more
51     * information about the different available clocks. The clock used by this method is
52     * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}).
53     *
54     * @return the current animation time in milliseconds
55     *
56     * @see android.os.SystemClock
57     */
58    public static long currentAnimationTimeMillis() {
59        return SystemClock.uptimeMillis();
60    }
61
62    /**
63     * Loads an {@link Animation} object from a resource
64     *
65     * @param context Application context used to access resources
66     * @param id The resource id of the animation to load
67     * @return The animation object reference by the specified id
68     * @throws NotFoundException when the animation cannot be loaded
69     */
70    public static Animation loadAnimation(Context context, @AnimRes int id)
71            throws NotFoundException {
72
73        XmlResourceParser parser = null;
74        try {
75            parser = context.getResources().getAnimation(id);
76            return createAnimationFromXml(context, parser);
77        } catch (XmlPullParserException ex) {
78            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
79                    Integer.toHexString(id));
80            rnf.initCause(ex);
81            throw rnf;
82        } catch (IOException ex) {
83            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
84                    Integer.toHexString(id));
85            rnf.initCause(ex);
86            throw rnf;
87        } finally {
88            if (parser != null) parser.close();
89        }
90    }
91
92    private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
93            throws XmlPullParserException, IOException {
94
95        return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
96    }
97
98    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
99            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
100
101        Animation anim = 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("set")) {
117                anim = new AnimationSet(c, attrs);
118                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
119            } else if (name.equals("alpha")) {
120                anim = new AlphaAnimation(c, attrs);
121            } else if (name.equals("scale")) {
122                anim = new ScaleAnimation(c, attrs);
123            }  else if (name.equals("rotate")) {
124                anim = new RotateAnimation(c, attrs);
125            }  else if (name.equals("translate")) {
126                anim = new TranslateAnimation(c, attrs);
127            } else {
128                throw new RuntimeException("Unknown animation name: " + parser.getName());
129            }
130
131            if (parent != null) {
132                parent.addAnimation(anim);
133            }
134        }
135
136        return anim;
137
138    }
139
140    /**
141     * Loads a {@link LayoutAnimationController} object from a resource
142     *
143     * @param context Application context used to access resources
144     * @param id The resource id of the animation to load
145     * @return The animation object reference by the specified id
146     * @throws NotFoundException when the layout animation controller cannot be loaded
147     */
148    public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id)
149            throws NotFoundException {
150
151        XmlResourceParser parser = null;
152        try {
153            parser = context.getResources().getAnimation(id);
154            return createLayoutAnimationFromXml(context, parser);
155        } catch (XmlPullParserException ex) {
156            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
157                    Integer.toHexString(id));
158            rnf.initCause(ex);
159            throw rnf;
160        } catch (IOException ex) {
161            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
162                    Integer.toHexString(id));
163            rnf.initCause(ex);
164            throw rnf;
165        } finally {
166            if (parser != null) parser.close();
167        }
168    }
169
170    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
171            XmlPullParser parser) throws XmlPullParserException, IOException {
172
173        return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
174    }
175
176    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
177            XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
178
179        LayoutAnimationController controller = null;
180
181        int type;
182        int depth = parser.getDepth();
183
184        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
185                && type != XmlPullParser.END_DOCUMENT) {
186
187            if (type != XmlPullParser.START_TAG) {
188                continue;
189            }
190
191            String name = parser.getName();
192
193            if ("layoutAnimation".equals(name)) {
194                controller = new LayoutAnimationController(c, attrs);
195            } else if ("gridLayoutAnimation".equals(name)) {
196                controller = new GridLayoutAnimationController(c, attrs);
197            } else {
198                throw new RuntimeException("Unknown layout animation name: " + name);
199            }
200        }
201
202        return controller;
203    }
204
205    /**
206     * Make an animation for objects becoming visible. Uses a slide and fade
207     * effect.
208     *
209     * @param c Context for loading resources
210     * @param fromLeft is the object to be animated coming from the left
211     * @return The new animation
212     */
213    public static Animation makeInAnimation(Context c, boolean fromLeft) {
214        Animation a;
215        if (fromLeft) {
216            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left);
217        } else {
218            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right);
219        }
220
221        a.setInterpolator(new DecelerateInterpolator());
222        a.setStartTime(currentAnimationTimeMillis());
223        return a;
224    }
225
226    /**
227     * Make an animation for objects becoming invisible. Uses a slide and fade
228     * effect.
229     *
230     * @param c Context for loading resources
231     * @param toRight is the object to be animated exiting to the right
232     * @return The new animation
233     */
234    public static Animation makeOutAnimation(Context c, boolean toRight) {
235        Animation a;
236        if (toRight) {
237            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right);
238        } else {
239            a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left);
240        }
241
242        a.setInterpolator(new AccelerateInterpolator());
243        a.setStartTime(currentAnimationTimeMillis());
244        return a;
245    }
246
247
248    /**
249     * Make an animation for objects becoming visible. Uses a slide up and fade
250     * effect.
251     *
252     * @param c Context for loading resources
253     * @return The new animation
254     */
255    public static Animation makeInChildBottomAnimation(Context c) {
256        Animation a;
257        a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom);
258        a.setInterpolator(new AccelerateInterpolator());
259        a.setStartTime(currentAnimationTimeMillis());
260        return a;
261    }
262
263    /**
264     * Loads an {@link Interpolator} object from a resource
265     *
266     * @param context Application context used to access resources
267     * @param id The resource id of the animation to load
268     * @return The animation object reference by the specified id
269     * @throws NotFoundException
270     */
271    public static Interpolator loadInterpolator(Context context, @AnimRes @InterpolatorRes int id)
272            throws NotFoundException {
273        XmlResourceParser parser = null;
274        try {
275            parser = context.getResources().getAnimation(id);
276            return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
277        } catch (XmlPullParserException ex) {
278            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
279                    Integer.toHexString(id));
280            rnf.initCause(ex);
281            throw rnf;
282        } catch (IOException ex) {
283            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
284                    Integer.toHexString(id));
285            rnf.initCause(ex);
286            throw rnf;
287        } finally {
288            if (parser != null) parser.close();
289        }
290
291    }
292
293    /**
294     * Loads an {@link Interpolator} object from a resource
295     *
296     * @param res The resources
297     * @param id The resource id of the animation to load
298     * @return The interpolator object reference by the specified id
299     * @throws NotFoundException
300     * @hide
301     */
302    public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException {
303        XmlResourceParser parser = null;
304        try {
305            parser = res.getAnimation(id);
306            return createInterpolatorFromXml(res, theme, parser);
307        } catch (XmlPullParserException ex) {
308            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
309                    Integer.toHexString(id));
310            rnf.initCause(ex);
311            throw rnf;
312        } catch (IOException ex) {
313            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
314                    Integer.toHexString(id));
315            rnf.initCause(ex);
316            throw rnf;
317        } finally {
318            if (parser != null)
319                parser.close();
320        }
321
322    }
323
324    private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
325            throws XmlPullParserException, IOException {
326
327        BaseInterpolator interpolator = null;
328
329        // Make sure we are on a start tag.
330        int type;
331        int depth = parser.getDepth();
332
333        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
334                && type != XmlPullParser.END_DOCUMENT) {
335
336            if (type != XmlPullParser.START_TAG) {
337                continue;
338            }
339
340            AttributeSet attrs = Xml.asAttributeSet(parser);
341
342            String name = parser.getName();
343
344            if (name.equals("linearInterpolator")) {
345                interpolator = new LinearInterpolator();
346            } else if (name.equals("accelerateInterpolator")) {
347                interpolator = new AccelerateInterpolator(res, theme, attrs);
348            } else if (name.equals("decelerateInterpolator")) {
349                interpolator = new DecelerateInterpolator(res, theme, attrs);
350            } else if (name.equals("accelerateDecelerateInterpolator")) {
351                interpolator = new AccelerateDecelerateInterpolator();
352            } else if (name.equals("cycleInterpolator")) {
353                interpolator = new CycleInterpolator(res, theme, attrs);
354            } else if (name.equals("anticipateInterpolator")) {
355                interpolator = new AnticipateInterpolator(res, theme, attrs);
356            } else if (name.equals("overshootInterpolator")) {
357                interpolator = new OvershootInterpolator(res, theme, attrs);
358            } else if (name.equals("anticipateOvershootInterpolator")) {
359                interpolator = new AnticipateOvershootInterpolator(res, theme, attrs);
360            } else if (name.equals("bounceInterpolator")) {
361                interpolator = new BounceInterpolator();
362            } else if (name.equals("pathInterpolator")) {
363                interpolator = new PathInterpolator(res, theme, attrs);
364            } else {
365                throw new RuntimeException("Unknown interpolator name: " + parser.getName());
366            }
367        }
368        return interpolator;
369    }
370}
371