AnimationDrawable.java revision 56ef127df75164243a21df10771e7c77c8ef2e24
1192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta/* 2192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * Copyright (C) 2006 The Android Open Source Project 3192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 4192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * Licensed under the Apache License, Version 2.0 (the "License"); 5192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * you may not use this file except in compliance with the License. 6192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * You may obtain a copy of the License at 7192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 8192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * http://www.apache.org/licenses/LICENSE-2.0 9192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 10192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * Unless required by applicable law or agreed to in writing, software 11192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * distributed under the License is distributed on an "AS IS" BASIS, 12192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * See the License for the specific language governing permissions and 14192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * limitations under the License. 15192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta */ 16192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 17192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptapackage android.graphics.drawable; 18192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 19192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport com.android.internal.R; 20192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 21192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport java.io.IOException; 22192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 23192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport org.xmlpull.v1.XmlPullParser; 24192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport org.xmlpull.v1.XmlPullParserException; 25192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 26192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport android.annotation.NonNull; 27192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport android.content.res.Resources; 28192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport android.content.res.TypedArray; 29192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport android.content.res.Resources.Theme; 30192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport android.os.SystemClock; 31192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptaimport android.util.AttributeSet; 32192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 33192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta/** 34192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * An object used to create frame-by-frame animations, defined by a series of 35192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * Drawable objects, which can be used as a View object's background. 36192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <p> 37192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * The simplest way to create a frame-by-frame animation is to define the 38192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * animation in an XML file, placed in the res/drawable/ folder, and set it as 39192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * the background to a View object. Then, call {@link #start()} to run the 40192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * animation. 41192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <p> 42192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * An AnimationDrawable defined in XML consists of a single 43192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * {@code <animation-list>} element and a series of nested 44192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * {@code <item>} tags. Each item defines a frame of the animation. See 45192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * the example below. 46192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <p> 47192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * spin_animation.xml file in res/drawable/ folder: 48192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <pre> 49192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <!-- Animation frames are wheel0.png through wheel5.png 50192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * files inside the res/drawable/ folder --> 51192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <animation-list android:id="@+id/selected" android:oneshot="false"> 52192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <item android:drawable="@drawable/wheel0" android:duration="50" /> 53192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <item android:drawable="@drawable/wheel1" android:duration="50" /> 54192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <item android:drawable="@drawable/wheel2" android:duration="50" /> 55192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <item android:drawable="@drawable/wheel3" android:duration="50" /> 56192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <item android:drawable="@drawable/wheel4" android:duration="50" /> 57192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <item android:drawable="@drawable/wheel5" android:duration="50" /> 58192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * </animation-list></pre> 59192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <p> 60192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * Here is the code to load and play this animation. 61192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <pre> 62192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * // Load the ImageView that will host the animation and 63192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * // set its background to our AnimationDrawable XML resource. 64192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * ImageView img = (ImageView)findViewById(R.id.spinning_wheel_image); 65192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * img.setBackgroundResource(R.drawable.spin_animation); 66192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 67192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * // Get the background, which has been compiled to an AnimationDrawable object. 68192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground(); 69192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 70192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * // Start the animation (looped playback by default). 71192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * frameAnimation.start(); 72192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * </pre> 73192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 74192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <div class="special reference"> 75192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <h3>Developer Guides</h3> 76192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <p>For more information about animating with {@code AnimationDrawable}, read the 77192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <a href="{@docRoot}guide/topics/graphics/drawable-animation.html">Drawable Animation</a> 78192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * developer guide.</p> 79192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * </div> 80192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * 81192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * @attr ref android.R.styleable#AnimationDrawable_visible 82192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * @attr ref android.R.styleable#AnimationDrawable_variablePadding 83192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * @attr ref android.R.styleable#AnimationDrawable_oneshot 84192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * @attr ref android.R.styleable#AnimationDrawableItem_duration 85192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * @attr ref android.R.styleable#AnimationDrawableItem_drawable 86192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta */ 87192d793d2586b620027edd5b45ff4c72a86cc7beHemant Guptapublic class AnimationDrawable extends DrawableContainer implements Runnable, Animatable { 88192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta private AnimationState mAnimationState; 89192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 90192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta /** The current frame, ranging from 0 to {@link #mAnimationState#getChildCount() - 1} */ 91192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta private int mCurFrame = 0; 92192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 93192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta /** Whether the drawable has an animation callback posted. */ 94192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta private boolean mRunning; 95192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 96192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta /** Whether the drawable should animate when visible. */ 97192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta private boolean mAnimating; 98192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 99192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta private boolean mMutated; 100192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 101192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta public AnimationDrawable() { 102192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta this(null, null); 103192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta } 104192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta 105192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta /** 106192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * Sets whether this AnimationDrawable is visible. 107192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * <p> 108192d793d2586b620027edd5b45ff4c72a86cc7beHemant Gupta * When the drawable becomes invisible, it will pause its animation. A 109 * subsequent change to visible with <code>restart</code> set to true will 110 * restart the animation from the first frame. If <code>restart</code> is 111 * false, the animation will resume from the most recent frame. 112 * 113 * @param visible true if visible, false otherwise 114 * @param restart when visible, true to force the animation to restart 115 * from the first frame 116 * @return true if the new visibility is different than its previous state 117 */ 118 @Override 119 public boolean setVisible(boolean visible, boolean restart) { 120 final boolean changed = super.setVisible(visible, restart); 121 if (visible) { 122 if (restart || changed) { 123 boolean startFromZero = restart || !mRunning || 124 mCurFrame >= mAnimationState.getChildCount(); 125 setFrame(startFromZero ? 0 : mCurFrame, true, mAnimating); 126 } 127 } else { 128 unscheduleSelf(this); 129 } 130 return changed; 131 } 132 133 /** 134 * Starts the animation, looping if necessary. This method has no effect 135 * if the animation is running. 136 * <p> 137 * <strong>Note:</strong> Do not call this in the 138 * {@link android.app.Activity#onCreate} method of your activity, because 139 * the {@link AnimationDrawable} is not yet fully attached to the window. 140 * If you want to play the animation immediately without requiring 141 * interaction, then you might want to call it from the 142 * {@link android.app.Activity#onWindowFocusChanged} method in your 143 * activity, which will get called when Android brings your window into 144 * focus. 145 * 146 * @see #isRunning() 147 * @see #stop() 148 */ 149 @Override 150 public void start() { 151 mAnimating = true; 152 153 if (!isRunning()) { 154 run(); 155 } 156 } 157 158 /** 159 * Stops the animation. This method has no effect if the animation is not 160 * running. 161 * 162 * @see #isRunning() 163 * @see #start() 164 */ 165 @Override 166 public void stop() { 167 mAnimating = false; 168 169 if (isRunning()) { 170 unscheduleSelf(this); 171 } 172 } 173 174 /** 175 * Indicates whether the animation is currently running or not. 176 * 177 * @return true if the animation is running, false otherwise 178 */ 179 @Override 180 public boolean isRunning() { 181 return mRunning; 182 } 183 184 /** 185 * This method exists for implementation purpose only and should not be 186 * called directly. Invoke {@link #start()} instead. 187 * 188 * @see #start() 189 */ 190 @Override 191 public void run() { 192 nextFrame(false); 193 } 194 195 @Override 196 public void unscheduleSelf(Runnable what) { 197 mCurFrame = 0; 198 mRunning = false; 199 super.unscheduleSelf(what); 200 } 201 202 /** 203 * @return The number of frames in the animation 204 */ 205 public int getNumberOfFrames() { 206 return mAnimationState.getChildCount(); 207 } 208 209 /** 210 * @return The Drawable at the specified frame index 211 */ 212 public Drawable getFrame(int index) { 213 return mAnimationState.getChild(index); 214 } 215 216 /** 217 * @return The duration in milliseconds of the frame at the 218 * specified index 219 */ 220 public int getDuration(int i) { 221 return mAnimationState.mDurations[i]; 222 } 223 224 /** 225 * @return True of the animation will play once, false otherwise 226 */ 227 public boolean isOneShot() { 228 return mAnimationState.mOneShot; 229 } 230 231 /** 232 * Sets whether the animation should play once or repeat. 233 * 234 * @param oneShot Pass true if the animation should only play once 235 */ 236 public void setOneShot(boolean oneShot) { 237 mAnimationState.mOneShot = oneShot; 238 } 239 240 /** 241 * Adds a frame to the animation 242 * 243 * @param frame The frame to add 244 * @param duration How long in milliseconds the frame should appear 245 */ 246 public void addFrame(@NonNull Drawable frame, int duration) { 247 mAnimationState.addFrame(frame, duration); 248 if (!mRunning) { 249 setFrame(0, true, false); 250 } 251 } 252 253 private void nextFrame(boolean unschedule) { 254 int nextFrame = mCurFrame + 1; 255 final int numFrames = mAnimationState.getChildCount(); 256 final boolean isLastFrame = mAnimationState.mOneShot && nextFrame >= (numFrames - 1); 257 258 // Loop if necessary. One-shot animations should never hit this case. 259 if (!mAnimationState.mOneShot && nextFrame >= numFrames) { 260 nextFrame = 0; 261 } 262 263 setFrame(nextFrame, unschedule, !isLastFrame); 264 } 265 266 private void setFrame(int frame, boolean unschedule, boolean animate) { 267 if (frame >= mAnimationState.getChildCount()) { 268 return; 269 } 270 mAnimating = animate; 271 mCurFrame = frame; 272 selectDrawable(frame); 273 if (unschedule || animate) { 274 unscheduleSelf(this); 275 } 276 if (animate) { 277 // Unscheduling may have clobbered these values; restore them 278 mCurFrame = frame; 279 mRunning = true; 280 scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); 281 } 282 } 283 284 @Override 285 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 286 throws XmlPullParserException, IOException { 287 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable); 288 super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible); 289 updateStateFromTypedArray(a); 290 a.recycle(); 291 292 inflateChildElements(r, parser, attrs, theme); 293 294 setFrame(0, true, false); 295 } 296 297 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 298 Theme theme) throws XmlPullParserException, IOException { 299 int type; 300 301 final int innerDepth = parser.getDepth()+1; 302 int depth; 303 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 304 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 305 if (type != XmlPullParser.START_TAG) { 306 continue; 307 } 308 309 if (depth > innerDepth || !parser.getName().equals("item")) { 310 continue; 311 } 312 313 final TypedArray a = obtainAttributes(r, theme, attrs, 314 R.styleable.AnimationDrawableItem); 315 316 final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1); 317 if (duration < 0) { 318 throw new XmlPullParserException(parser.getPositionDescription() 319 + ": <item> tag requires a 'duration' attribute"); 320 } 321 322 Drawable dr = a.getDrawable(R.styleable.AnimationDrawableItem_drawable); 323 324 a.recycle(); 325 326 if (dr == null) { 327 while ((type=parser.next()) == XmlPullParser.TEXT) { 328 // Empty 329 } 330 if (type != XmlPullParser.START_TAG) { 331 throw new XmlPullParserException(parser.getPositionDescription() 332 + ": <item> tag requires a 'drawable' attribute or child tag" 333 + " defining a drawable"); 334 } 335 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 336 } 337 338 mAnimationState.addFrame(dr, duration); 339 if (dr != null) { 340 dr.setCallback(this); 341 } 342 } 343 } 344 345 private void updateStateFromTypedArray(TypedArray a) { 346 mAnimationState.mVariablePadding = a.getBoolean( 347 R.styleable.AnimationDrawable_variablePadding, mAnimationState.mVariablePadding); 348 349 mAnimationState.mOneShot = a.getBoolean( 350 R.styleable.AnimationDrawable_oneshot, mAnimationState.mOneShot); 351 } 352 353 @Override 354 @NonNull 355 public Drawable mutate() { 356 if (!mMutated && super.mutate() == this) { 357 mAnimationState.mutate(); 358 mMutated = true; 359 } 360 return this; 361 } 362 363 @Override 364 AnimationState cloneConstantState() { 365 return new AnimationState(mAnimationState, this, null); 366 } 367 368 /** 369 * @hide 370 */ 371 public void clearMutated() { 372 super.clearMutated(); 373 mMutated = false; 374 } 375 376 private final static class AnimationState extends DrawableContainerState { 377 private int[] mDurations; 378 private boolean mOneShot = false; 379 380 AnimationState(AnimationState orig, AnimationDrawable owner, Resources res) { 381 super(orig, owner, res); 382 383 if (orig != null) { 384 mDurations = orig.mDurations; 385 mOneShot = orig.mOneShot; 386 } else { 387 mDurations = new int[getCapacity()]; 388 mOneShot = false; 389 } 390 } 391 392 private void mutate() { 393 mDurations = mDurations.clone(); 394 } 395 396 @Override 397 public Drawable newDrawable() { 398 return new AnimationDrawable(this, null); 399 } 400 401 @Override 402 public Drawable newDrawable(Resources res) { 403 return new AnimationDrawable(this, res); 404 } 405 406 public void addFrame(Drawable dr, int dur) { 407 // Do not combine the following. The array index must be evaluated before 408 // the array is accessed because super.addChild(dr) has a side effect on mDurations. 409 int pos = super.addChild(dr); 410 mDurations[pos] = dur; 411 } 412 413 @Override 414 public void growArray(int oldSize, int newSize) { 415 super.growArray(oldSize, newSize); 416 int[] newDurations = new int[newSize]; 417 System.arraycopy(mDurations, 0, newDurations, 0, oldSize); 418 mDurations = newDurations; 419 } 420 } 421 422 @Override 423 protected void setConstantState(@NonNull DrawableContainerState state) { 424 super.setConstantState(state); 425 426 if (state instanceof AnimationState) { 427 mAnimationState = (AnimationState) state; 428 } 429 } 430 431 private AnimationDrawable(AnimationState state, Resources res) { 432 final AnimationState as = new AnimationState(state, this, res); 433 setConstantState(as); 434 if (state != null) { 435 setFrame(0, true, false); 436 } 437 } 438} 439 440