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