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