AnimationDrawable.java revision f5534a0785db9dfa239d8b6c20b011993742d534
1/*
2 * Copyright (C) 2006 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.graphics.drawable;
18
19import java.io.IOException;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.os.SystemClock;
27import android.util.AttributeSet;
28
29/**
30 *
31 * An object used to create frame-by-frame animations, defined by a series of Drawable objects,
32 * which can be used as a View object's background.
33 * <p>
34 * The simplest way to create a frame-by-frame animation is to define the animation in an XML
35 * file, placed in the res/drawable/ folder, and set it as the background to a View object. Then, call
36 * {@link #run()} to start the animation.
37 * <p>
38 * An AnimationDrawable defined in XML consists of a single <code>&lt;animation-list></code> element,
39 * and a series of nested <code>&lt;item></code> tags. Each item defines a frame of the animation.
40 * See the example below.
41 * </p>
42 * <p>spin_animation.xml file in res/drawable/ folder:</p>
43 * <pre>&lt;!-- Animation frames are wheel0.png -- wheel5.png files inside the
44 * res/drawable/ folder --&gt;
45 * &lt;animation-list android:id=&quot;selected&quot; android:oneshot=&quot;false&quot;&gt;
46 *    &lt;item android:drawable=&quot;@drawable/wheel0&quot; android:duration=&quot;50&quot; /&gt;
47 *    &lt;item android:drawable=&quot;@drawable/wheel1&quot; android:duration=&quot;50&quot; /&gt;
48 *    &lt;item android:drawable=&quot;@drawable/wheel2&quot; android:duration=&quot;50&quot; /&gt;
49 *    &lt;item android:drawable=&quot;@drawable/wheel3&quot; android:duration=&quot;50&quot; /&gt;
50 *    &lt;item android:drawable=&quot;@drawable/wheel4&quot; android:duration=&quot;50&quot; /&gt;
51 *    &lt;item android:drawable=&quot;@drawable/wheel5&quot; android:duration=&quot;50&quot; /&gt;
52 * &lt;/animation-list&gt;</pre>
53 *
54 * <p>Here is the code to load and play this animation.</p>
55 * <pre>
56 * // Load the ImageView that will host the animation and
57 * // set its background to our AnimationDrawable XML resource.
58 * ImageView img = (ImageView)findViewById(R.id.spinning_wheel_image);
59 * img.setBackgroundResource(R.drawable.spin_animation);
60 *
61 * // Get the background, which has been compiled to an AnimationDrawable object.
62 * AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground();
63 *
64 * // Start the animation (looped playback by default).
65 * frameAnimation.start();
66 * </pre>
67 * <p>For more information, see the guide to <a
68 * href="{@docRoot}guide/topics/resources/animation-resource.html">Animation Resources</a>.</p>
69 *
70 * @attr ref android.R.styleable#AnimationDrawable_visible
71 * @attr ref android.R.styleable#AnimationDrawable_variablePadding
72 * @attr ref android.R.styleable#AnimationDrawable_oneshot
73 * @attr ref android.R.styleable#AnimationDrawableItem_duration
74 * @attr ref android.R.styleable#AnimationDrawableItem_drawable
75 */
76public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
77    private final AnimationState mAnimationState;
78    private int mCurFrame = -1;
79    private boolean mMutated;
80
81    public AnimationDrawable() {
82        this(null, null);
83    }
84
85    @Override
86    public boolean setVisible(boolean visible, boolean restart) {
87        boolean changed = super.setVisible(visible, restart);
88        if (visible) {
89            if (changed || restart) {
90                setFrame(0, true, true);
91            }
92        } else {
93            unscheduleSelf(this);
94        }
95        return changed;
96    }
97
98    /**
99     * <p>Starts the animation, looping if necessary. This method has no effect
100     * if the animation is running.</p>
101     *
102     * @see #isRunning()
103     * @see #stop()
104     */
105    public void start() {
106        if (!isRunning()) {
107            run();
108        }
109    }
110
111    /**
112     * <p>Stops the animation. This method has no effect if the animation is
113     * not running.</p>
114     *
115     * @see #isRunning()
116     * @see #start()
117     */
118    public void stop() {
119        if (isRunning()) {
120            unscheduleSelf(this);
121        }
122    }
123
124    /**
125     * <p>Indicates whether the animation is currently running or not.</p>
126     *
127     * @return true if the animation is running, false otherwise
128     */
129    public boolean isRunning() {
130        return mCurFrame > -1;
131    }
132
133    /**
134     * <p>This method exists for implementation purpose only and should not be
135     * called directly. Invoke {@link #start()} instead.</p>
136     *
137     * @see #start()
138     */
139    public void run() {
140        nextFrame(false);
141    }
142
143    @Override
144    public void unscheduleSelf(Runnable what) {
145        mCurFrame = -1;
146        super.unscheduleSelf(what);
147    }
148
149    /**
150     * @return The number of frames in the animation
151     */
152    public int getNumberOfFrames() {
153        return mAnimationState.getChildCount();
154    }
155
156    /**
157     * @return The Drawable at the specified frame index
158     */
159    public Drawable getFrame(int index) {
160        return mAnimationState.getChildren()[index];
161    }
162
163    /**
164     * @return The duration in milliseconds of the frame at the
165     * specified index
166     */
167    public int getDuration(int i) {
168        return mAnimationState.mDurations[i];
169    }
170
171    /**
172     * @return True of the animation will play once, false otherwise
173     */
174    public boolean isOneShot() {
175        return mAnimationState.mOneShot;
176    }
177
178    /**
179     * Sets whether the animation should play once or repeat.
180     *
181     * @param oneShot Pass true if the animation should only play once
182     */
183    public void setOneShot(boolean oneShot) {
184        mAnimationState.mOneShot = oneShot;
185    }
186
187    /**
188     * Add a frame to the animation
189     *
190     * @param frame The frame to add
191     * @param duration How long in milliseconds the frame should appear
192     */
193    public void addFrame(Drawable frame, int duration) {
194        mAnimationState.addFrame(frame, duration);
195        if (mCurFrame < 0) {
196            setFrame(0, true, false);
197        }
198    }
199
200    private void nextFrame(boolean unschedule) {
201        int next = mCurFrame+1;
202        final int N = mAnimationState.getChildCount();
203        if (next >= N) {
204            next = 0;
205        }
206        setFrame(next, unschedule, !mAnimationState.mOneShot || next < (N - 1));
207    }
208
209    private void setFrame(int frame, boolean unschedule, boolean animate) {
210        if (frame >= mAnimationState.getChildCount()) {
211            return;
212        }
213        mCurFrame = frame;
214        selectDrawable(frame);
215        if (unschedule) {
216            unscheduleSelf(this);
217        }
218        if (animate) {
219            scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
220        }
221    }
222
223    @Override
224    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
225            throws XmlPullParserException, IOException {
226
227        TypedArray a = r.obtainAttributes(attrs,
228                com.android.internal.R.styleable.AnimationDrawable);
229
230        super.inflateWithAttributes(r, parser, a,
231                com.android.internal.R.styleable.AnimationDrawable_visible);
232
233        mAnimationState.setVariablePadding(a.getBoolean(
234                com.android.internal.R.styleable.AnimationDrawable_variablePadding, false));
235
236        mAnimationState.mOneShot = a.getBoolean(
237                com.android.internal.R.styleable.AnimationDrawable_oneshot, false);
238
239        a.recycle();
240
241        int type;
242
243        final int innerDepth = parser.getDepth()+1;
244        int depth;
245        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT &&
246                ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
247            if (type != XmlPullParser.START_TAG) {
248                continue;
249            }
250
251            if (depth > innerDepth || !parser.getName().equals("item")) {
252                continue;
253            }
254
255            a = r.obtainAttributes(attrs, com.android.internal.R.styleable.AnimationDrawableItem);
256            int duration = a.getInt(
257                    com.android.internal.R.styleable.AnimationDrawableItem_duration, -1);
258            if (duration < 0) {
259                throw new XmlPullParserException(
260                        parser.getPositionDescription()
261                        + ": <item> tag requires a 'duration' attribute");
262            }
263            int drawableRes = a.getResourceId(
264                    com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0);
265
266            a.recycle();
267
268            Drawable dr;
269            if (drawableRes != 0) {
270                dr = r.getDrawable(drawableRes);
271            } else {
272                while ((type=parser.next()) == XmlPullParser.TEXT) {
273                    // Empty
274                }
275                if (type != XmlPullParser.START_TAG) {
276                    throw new XmlPullParserException(parser.getPositionDescription() +
277                            ": <item> tag requires a 'drawable' attribute or child tag" +
278                            " defining a drawable");
279                }
280                dr = Drawable.createFromXmlInner(r, parser, attrs);
281            }
282
283            mAnimationState.addFrame(dr, duration);
284            if (dr != null) {
285                dr.setCallback(this);
286            }
287        }
288
289        setFrame(0, true, false);
290    }
291
292    @Override
293    public Drawable mutate() {
294        if (!mMutated && super.mutate() == this) {
295            mAnimationState.mDurations = mAnimationState.mDurations.clone();
296            mMutated = true;
297        }
298        return this;
299    }
300
301    private final static class AnimationState extends DrawableContainerState {
302        private int[] mDurations;
303        private boolean mOneShot;
304
305        AnimationState(AnimationState orig, AnimationDrawable owner,
306                Resources res) {
307            super(orig, owner, res);
308
309            if (orig != null) {
310                mDurations = orig.mDurations;
311                mOneShot = orig.mOneShot;
312            } else {
313                mDurations = new int[getChildren().length];
314                mOneShot = true;
315            }
316        }
317
318        @Override
319        public Drawable newDrawable() {
320            return new AnimationDrawable(this, null);
321        }
322
323        @Override
324        public Drawable newDrawable(Resources res) {
325            return new AnimationDrawable(this, res);
326        }
327
328        public void addFrame(Drawable dr, int dur) {
329            // Do not combine the following. The array index must be evaluated before
330            // the array is accessed because super.addChild(dr) has a side effect on mDurations.
331            int pos = super.addChild(dr);
332            mDurations[pos] = dur;
333        }
334
335        @Override
336        public void growArray(int oldSize, int newSize) {
337            super.growArray(oldSize, newSize);
338            int[] newDurations = new int[newSize];
339            System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
340            mDurations = newDurations;
341        }
342    }
343
344    private AnimationDrawable(AnimationState state, Resources res) {
345        AnimationState as = new AnimationState(state, this, res);
346        mAnimationState = as;
347        setConstantState(as);
348        if (state != null) {
349            setFrame(0, true, false);
350        }
351    }
352}
353
354