• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.graphics.drawable;
18 
19 import java.io.IOException;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.os.SystemClock;
27 import 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  *
68  * @attr ref android.R.styleable#AnimationDrawable_visible
69  * @attr ref android.R.styleable#AnimationDrawable_variablePadding
70  * @attr ref android.R.styleable#AnimationDrawable_oneshot
71  * @attr ref android.R.styleable#AnimationDrawableItem_duration
72  * @attr ref android.R.styleable#AnimationDrawableItem_drawable
73  */
74 public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
75     private final AnimationState mAnimationState;
76     private int mCurFrame = -1;
77     private boolean mMutated;
78 
AnimationDrawable()79     public AnimationDrawable() {
80         this(null, null);
81     }
82 
83     @Override
setVisible(boolean visible, boolean restart)84     public boolean setVisible(boolean visible, boolean restart) {
85         boolean changed = super.setVisible(visible, restart);
86         if (visible) {
87             if (changed || restart) {
88                 setFrame(0, true, true);
89             }
90         } else {
91             unscheduleSelf(this);
92         }
93         return changed;
94     }
95 
96     /**
97      * <p>Starts the animation, looping if necessary. This method has no effect
98      * if the animation is running.</p>
99      *
100      * @see #isRunning()
101      * @see #stop()
102      */
start()103     public void start() {
104         if (!isRunning()) {
105             run();
106         }
107     }
108 
109     /**
110      * <p>Stops the animation. This method has no effect if the animation is
111      * not running.</p>
112      *
113      * @see #isRunning()
114      * @see #start()
115      */
stop()116     public void stop() {
117         if (isRunning()) {
118             unscheduleSelf(this);
119         }
120     }
121 
122     /**
123      * <p>Indicates whether the animation is currently running or not.</p>
124      *
125      * @return true if the animation is running, false otherwise
126      */
isRunning()127     public boolean isRunning() {
128         return mCurFrame > -1;
129     }
130 
131     /**
132      * <p>This method exists for implementation purpose only and should not be
133      * called directly. Invoke {@link #start()} instead.</p>
134      *
135      * @see #start()
136      */
run()137     public void run() {
138         nextFrame(false);
139     }
140 
141     @Override
unscheduleSelf(Runnable what)142     public void unscheduleSelf(Runnable what) {
143         mCurFrame = -1;
144         super.unscheduleSelf(what);
145     }
146 
147     /**
148      * @return The number of frames in the animation
149      */
getNumberOfFrames()150     public int getNumberOfFrames() {
151         return mAnimationState.getChildCount();
152     }
153 
154     /**
155      * @return The Drawable at the specified frame index
156      */
getFrame(int index)157     public Drawable getFrame(int index) {
158         return mAnimationState.getChildren()[index];
159     }
160 
161     /**
162      * @return The duration in milliseconds of the frame at the
163      * specified index
164      */
getDuration(int i)165     public int getDuration(int i) {
166         return mAnimationState.mDurations[i];
167     }
168 
169     /**
170      * @return True of the animation will play once, false otherwise
171      */
isOneShot()172     public boolean isOneShot() {
173         return mAnimationState.mOneShot;
174     }
175 
176     /**
177      * Sets whether the animation should play once or repeat.
178      *
179      * @param oneShot Pass true if the animation should only play once
180      */
setOneShot(boolean oneShot)181     public void setOneShot(boolean oneShot) {
182         mAnimationState.mOneShot = oneShot;
183     }
184 
185     /**
186      * Add a frame to the animation
187      *
188      * @param frame The frame to add
189      * @param duration How long in milliseconds the frame should appear
190      */
addFrame(Drawable frame, int duration)191     public void addFrame(Drawable frame, int duration) {
192         mAnimationState.addFrame(frame, duration);
193     }
194 
nextFrame(boolean unschedule)195     private void nextFrame(boolean unschedule) {
196         int next = mCurFrame+1;
197         final int N = mAnimationState.getChildCount();
198         if (next >= N) {
199             next = 0;
200         }
201         setFrame(next, unschedule, !mAnimationState.mOneShot || next < (N - 1));
202     }
203 
setFrame(int frame, boolean unschedule, boolean animate)204     private void setFrame(int frame, boolean unschedule, boolean animate) {
205         if (frame >= mAnimationState.getChildCount()) {
206             return;
207         }
208         mCurFrame = frame;
209         selectDrawable(frame);
210         if (unschedule) {
211             unscheduleSelf(this);
212         }
213         if (animate) {
214             scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
215         }
216     }
217 
218     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs)219     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
220             throws XmlPullParserException, IOException {
221 
222         TypedArray a = r.obtainAttributes(attrs,
223                 com.android.internal.R.styleable.AnimationDrawable);
224 
225         super.inflateWithAttributes(r, parser, a,
226                 com.android.internal.R.styleable.AnimationDrawable_visible);
227 
228         mAnimationState.setVariablePadding(a.getBoolean(
229                 com.android.internal.R.styleable.AnimationDrawable_variablePadding, false));
230 
231         mAnimationState.mOneShot = a.getBoolean(
232                 com.android.internal.R.styleable.AnimationDrawable_oneshot, false);
233 
234         a.recycle();
235 
236         int type;
237 
238         final int innerDepth = parser.getDepth()+1;
239         int depth;
240         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT &&
241                 ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
242             if (type != XmlPullParser.START_TAG) {
243                 continue;
244             }
245 
246             if (depth > innerDepth || !parser.getName().equals("item")) {
247                 continue;
248             }
249 
250             a = r.obtainAttributes(attrs, com.android.internal.R.styleable.AnimationDrawableItem);
251             int duration = a.getInt(
252                     com.android.internal.R.styleable.AnimationDrawableItem_duration, -1);
253             if (duration < 0) {
254                 throw new XmlPullParserException(
255                         parser.getPositionDescription()
256                         + ": <item> tag requires a 'duration' attribute");
257             }
258             int drawableRes = a.getResourceId(
259                     com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0);
260 
261             a.recycle();
262 
263             Drawable dr;
264             if (drawableRes != 0) {
265                 dr = r.getDrawable(drawableRes);
266             } else {
267                 while ((type=parser.next()) == XmlPullParser.TEXT) {
268                     // Empty
269                 }
270                 if (type != XmlPullParser.START_TAG) {
271                     throw new XmlPullParserException(parser.getPositionDescription() +
272                             ": <item> tag requires a 'drawable' attribute or child tag" +
273                             " defining a drawable");
274                 }
275                 dr = Drawable.createFromXmlInner(r, parser, attrs);
276             }
277 
278             mAnimationState.addFrame(dr, duration);
279             if (dr != null) {
280                 dr.setCallback(this);
281             }
282         }
283 
284         setFrame(0, true, false);
285     }
286 
287     @Override
mutate()288     public Drawable mutate() {
289         if (!mMutated && super.mutate() == this) {
290             mAnimationState.mDurations = mAnimationState.mDurations.clone();
291             mMutated = true;
292         }
293         return this;
294     }
295 
296     private final static class AnimationState extends DrawableContainerState {
297         private int[] mDurations;
298         private boolean mOneShot;
299 
AnimationState(AnimationState orig, AnimationDrawable owner, Resources res)300         AnimationState(AnimationState orig, AnimationDrawable owner,
301                 Resources res) {
302             super(orig, owner, res);
303 
304             if (orig != null) {
305                 mDurations = orig.mDurations;
306                 mOneShot = orig.mOneShot;
307             } else {
308                 mDurations = new int[getChildren().length];
309                 mOneShot = true;
310             }
311         }
312 
313         @Override
newDrawable()314         public Drawable newDrawable() {
315             return new AnimationDrawable(this, null);
316         }
317 
318         @Override
newDrawable(Resources res)319         public Drawable newDrawable(Resources res) {
320             return new AnimationDrawable(this, res);
321         }
322 
addFrame(Drawable dr, int dur)323         public void addFrame(Drawable dr, int dur) {
324             // Do not combine the following. The array index must be evaluated before
325             // the array is accessed because super.addChild(dr) has a side effect on mDurations.
326             int pos = super.addChild(dr);
327             mDurations[pos] = dur;
328         }
329 
330         @Override
growArray(int oldSize, int newSize)331         public void growArray(int oldSize, int newSize) {
332             super.growArray(oldSize, newSize);
333             int[] newDurations = new int[newSize];
334             System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
335             mDurations = newDurations;
336         }
337     }
338 
AnimationDrawable(AnimationState state, Resources res)339     private AnimationDrawable(AnimationState state, Resources res) {
340         AnimationState as = new AnimationState(state, this, res);
341         mAnimationState = as;
342         setConstantState(as);
343         if (state != null) {
344             setFrame(0, true, false);
345         }
346     }
347 }
348 
349