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