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><animation-list></code> element, 39 * and a series of nested <code><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><!-- Animation frames are wheel0.png -- wheel5.png files inside the 44 * res/drawable/ folder --> 45 * <animation-list android:id="selected" android:oneshot="false"> 46 * <item android:drawable="@drawable/wheel0" android:duration="50" /> 47 * <item android:drawable="@drawable/wheel1" android:duration="50" /> 48 * <item android:drawable="@drawable/wheel2" android:duration="50" /> 49 * <item android:drawable="@drawable/wheel3" android:duration="50" /> 50 * <item android:drawable="@drawable/wheel4" android:duration="50" /> 51 * <item android:drawable="@drawable/wheel5" android:duration="50" /> 52 * </animation-list></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