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