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