AnimationDrawable.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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 * 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 */ 74public class AnimationDrawable extends DrawableContainer implements Runnable { 75 private final AnimationState mAnimationState; 76 private int mCurFrame = -1; 77 private boolean mMutated; 78 79 public AnimationDrawable() { 80 this(null); 81 } 82 83 @Override 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 */ 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 */ 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 */ 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 */ 137 public void run() { 138 nextFrame(false); 139 } 140 141 @Override 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 */ 150 public int getNumberOfFrames() { 151 return mAnimationState.getChildCount(); 152 } 153 154 /** 155 * @return The Drawable at the specified frame index 156 */ 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 */ 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 */ 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 */ 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 */ 191 public void addFrame(Drawable frame, int duration) { 192 mAnimationState.addFrame(frame, duration); 193 } 194 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 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 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 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 300 AnimationState(AnimationState orig, AnimationDrawable owner) { 301 super(orig, owner); 302 303 if (orig != null) { 304 mDurations = orig.mDurations; 305 mOneShot = orig.mOneShot; 306 } else { 307 mDurations = new int[getChildren().length]; 308 mOneShot = true; 309 } 310 } 311 312 @Override 313 public Drawable newDrawable() { 314 return new AnimationDrawable(this); 315 } 316 317 public void addFrame(Drawable dr, int dur) { 318 // Do not combine the following. The array index must be evaluated before 319 // the array is accessed because super.addChild(dr) has a side effect on mDurations. 320 int pos = super.addChild(dr); 321 mDurations[pos] = dur; 322 } 323 324 @Override 325 public void growArray(int oldSize, int newSize) { 326 super.growArray(oldSize, newSize); 327 int[] newDurations = new int[newSize]; 328 System.arraycopy(mDurations, 0, newDurations, 0, oldSize); 329 mDurations = newDurations; 330 } 331 } 332 333 private AnimationDrawable(AnimationState state) { 334 AnimationState as = new AnimationState(state, this); 335 mAnimationState = as; 336 setConstantState(as); 337 if (state != null) { 338 setFrame(0, true, false); 339 } 340 } 341} 342 343