/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics.drawable; import com.android.internal.R; import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.os.SystemClock; import android.util.AttributeSet; /** * An object used to create frame-by-frame animations, defined by a series of * Drawable objects, which can be used as a View object's background. *
* The simplest way to create a frame-by-frame animation is to define the * animation in an XML file, placed in the res/drawable/ folder, and set it as * the background to a View object. Then, call {@link #start()} to run the * animation. *
* An AnimationDrawable defined in XML consists of a single
* {@code
* spin_animation.xml file in res/drawable/ folder:
*
* Here is the code to load and play this animation.
* For more information about animating with {@code AnimationDrawable}, read the
* Drawable Animation
* developer guide.
* When the drawable becomes invisible, it will pause its animation. A subsequent change to
* visible with
* Note: Do not call this in the
* {@link android.app.Activity#onCreate} method of your activity, because
* the {@link AnimationDrawable} is not yet fully attached to the window.
* If you want to play the animation immediately without requiring
* interaction, then you might want to call it from the
* {@link android.app.Activity#onWindowFocusChanged} method in your
* activity, which will get called when Android brings your window into
* focus.
*
* @see #isRunning()
* @see #stop()
*/
@Override
public void start() {
mAnimating = true;
if (!isRunning()) {
// Start from 0th frame.
setFrame(0, false, mAnimationState.getChildCount() > 1
|| !mAnimationState.mOneShot);
}
}
/**
* Stops the animation at the current frame. This method has no effect if the animation is not
* running.
*
* @see #isRunning()
* @see #start()
*/
@Override
public void stop() {
mAnimating = false;
if (isRunning()) {
mCurFrame = 0;
unscheduleSelf(this);
}
}
/**
* Indicates whether the animation is currently running or not.
*
* @return true if the animation is running, false otherwise
*/
@Override
public boolean isRunning() {
return mRunning;
}
/**
* This method exists for implementation purpose only and should not be
* called directly. Invoke {@link #start()} instead.
*
* @see #start()
*/
@Override
public void run() {
nextFrame(false);
}
@Override
public void unscheduleSelf(Runnable what) {
mRunning = false;
super.unscheduleSelf(what);
}
/**
* @return The number of frames in the animation
*/
public int getNumberOfFrames() {
return mAnimationState.getChildCount();
}
/**
* @return The Drawable at the specified frame index
*/
public Drawable getFrame(int index) {
return mAnimationState.getChild(index);
}
/**
* @return The duration in milliseconds of the frame at the
* specified index
*/
public int getDuration(int i) {
return mAnimationState.mDurations[i];
}
/**
* @return True of the animation will play once, false otherwise
*/
public boolean isOneShot() {
return mAnimationState.mOneShot;
}
/**
* Sets whether the animation should play once or repeat.
*
* @param oneShot Pass true if the animation should only play once
*/
public void setOneShot(boolean oneShot) {
mAnimationState.mOneShot = oneShot;
}
/**
* Adds a frame to the animation
*
* @param frame The frame to add
* @param duration How long in milliseconds the frame should appear
*/
public void addFrame(@NonNull Drawable frame, int duration) {
mAnimationState.addFrame(frame, duration);
if (!mRunning) {
setFrame(0, true, false);
}
}
private void nextFrame(boolean unschedule) {
int nextFrame = mCurFrame + 1;
final int numFrames = mAnimationState.getChildCount();
final boolean isLastFrame = mAnimationState.mOneShot && nextFrame >= (numFrames - 1);
// Loop if necessary. One-shot animations should never hit this case.
if (!mAnimationState.mOneShot && nextFrame >= numFrames) {
nextFrame = 0;
}
setFrame(nextFrame, unschedule, !isLastFrame);
}
private void setFrame(int frame, boolean unschedule, boolean animate) {
if (frame >= mAnimationState.getChildCount()) {
return;
}
mAnimating = animate;
mCurFrame = frame;
selectDrawable(frame);
if (unschedule || animate) {
unscheduleSelf(this);
}
if (animate) {
// Unscheduling may have clobbered these values; restore them
mCurFrame = frame;
mRunning = true;
scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
}
}
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable);
super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible);
updateStateFromTypedArray(a);
updateDensity(r);
a.recycle();
inflateChildElements(r, parser, attrs, theme);
setFrame(0, true, false);
}
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
int type;
final int innerDepth = parser.getDepth()+1;
int depth;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.AnimationDrawableItem);
final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1);
if (duration < 0) {
throw new XmlPullParserException(parser.getPositionDescription()
+ ":
* <!-- Animation frames are wheel0.png through wheel5.png
* files inside the res/drawable/ folder -->
* <animation-list android:id="@+id/selected" android:oneshot="false">
* <item android:drawable="@drawable/wheel0" android:duration="50" />
* <item android:drawable="@drawable/wheel1" android:duration="50" />
* <item android:drawable="@drawable/wheel2" android:duration="50" />
* <item android:drawable="@drawable/wheel3" android:duration="50" />
* <item android:drawable="@drawable/wheel4" android:duration="50" />
* <item android:drawable="@drawable/wheel5" android:duration="50" />
* </animation-list>
*
* // Load the ImageView that will host the animation and
* // set its background to our AnimationDrawable XML resource.
* ImageView img = (ImageView)findViewById(R.id.spinning_wheel_image);
* img.setBackgroundResource(R.drawable.spin_animation);
*
* // Get the background, which has been compiled to an AnimationDrawable object.
* AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground();
*
* // Start the animation (looped playback by default).
* frameAnimation.start();
*
*
* Developer Guides
* restart
set to true will restart the animation from the
* first frame. If restart
is false, the drawable will resume from the most recent
* frame. If the drawable has already reached the last frame, it will then loop back to the
* first frame, unless it's a one shot drawable (set through {@link #setOneShot(boolean)}),
* in which case, it will stay on the last frame.
*
* @param visible true if visible, false otherwise
* @param restart when visible, true to force the animation to restart
* from the first frame
* @return true if the new visibility is different than its previous state
*/
@Override
public boolean setVisible(boolean visible, boolean restart) {
final boolean changed = super.setVisible(visible, restart);
if (visible) {
if (restart || changed) {
boolean startFromZero = restart || (!mRunning && !mAnimationState.mOneShot) ||
mCurFrame >= mAnimationState.getChildCount();
setFrame(startFromZero ? 0 : mCurFrame, true, mAnimating);
}
} else {
unscheduleSelf(this);
}
return changed;
}
/**
* Starts the animation from the first frame, looping if necessary. This method has no effect
* if the animation is running.
*