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><!-- 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>// 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