diff --git a/src/api-impl/android/graphics/drawable/AnimationDrawable.java b/src/api-impl/android/graphics/drawable/AnimationDrawable.java index daf18c3a..59e6e409 100644 --- a/src/api-impl/android/graphics/drawable/AnimationDrawable.java +++ b/src/api-impl/android/graphics/drawable/AnimationDrawable.java @@ -1,21 +1,362 @@ +/* + * 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; -public class AnimationDrawable extends Drawable { - private int num_frames = 0; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.SystemClock; +import android.util.AttributeSet; +import java.io.IOException; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; - public int getNumberOfFrames() { - return num_frames; +/** + * + * 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 <animation-list> element, + * and a series of nested <item> tags. Each item defines a frame of the animation. + * See the example below. + *

+ *

spin_animation.xml file in res/drawable/ folder:

+ *
<!-- Animation frames are wheel0.png -- 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>
+ * + *

Here is the code to load and play this animation.

+ *
+ * // 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

+ *

For more information about animating with {@code AnimationDrawable}, read the + * Drawable Animation + * developer guide.

+ *
+ * + * @attr ref android.R.styleable#AnimationDrawable_visible + * @attr ref android.R.styleable#AnimationDrawable_variablePadding + * @attr ref android.R.styleable#AnimationDrawable_oneshot + * @attr ref android.R.styleable#AnimationDrawableItem_duration + * @attr ref android.R.styleable#AnimationDrawableItem_drawable + */ +public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable { + private final AnimationState mAnimationState; + private int mCurFrame = -1; + private boolean mMutated; + + public AnimationDrawable() { + this(null, null); } - public void addFrame(Drawable drawable, int duration) { - num_frames++; + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (visible) { + if (changed || restart) { + setFrame(0, true, true); + } + } else { + unscheduleSelf(this); + } + return changed; } + + /** + *

Starts the animation, looping if necessary. This method has no effect + * if the animation is running. Do not call this in the {@link android.app.Activity#onCreate} + * method of your activity, because the {@link android.graphics.drawable.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() + */ public void start() { - for(int i = 0; i < num_frames; i++) { + if (!isRunning()) { run(); } } - public void stop() {} - public void run() {} + /** + *

Stops the animation. This method has no effect if the animation is + * not running.

+ * + * @see #isRunning() + * @see #start() + */ + public void stop() { + if (isRunning()) { + unscheduleSelf(this); + } + } + + /** + *

Indicates whether the animation is currently running or not.

+ * + * @return true if the animation is running, false otherwise + */ + public boolean isRunning() { + return mCurFrame > -1; + } + + /** + *

This method exists for implementation purpose only and should not be + * called directly. Invoke {@link #start()} instead.

+ * + * @see #start() + */ + public void run() { + nextFrame(false); + } + +// @Override + public void unscheduleSelf(Runnable what) { + mCurFrame = -1; + 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; + } + + /** + * Add a frame to the animation + * + * @param frame The frame to add + * @param duration How long in milliseconds the frame should appear + */ + public void addFrame(Drawable frame, int duration) { + mAnimationState.addFrame(frame, duration); + if (mCurFrame < 0) { + setFrame(0, true, false); + } + } + + private void nextFrame(boolean unschedule) { + int next = mCurFrame + 1; + final int N = mAnimationState.getChildCount(); + if (next >= N) { + next = 0; + } + setFrame(next, unschedule, !mAnimationState.mOneShot || next < (N - 1)); + } + + private void setFrame(int frame, boolean unschedule, boolean animate) { + if (frame >= mAnimationState.getChildCount()) { + return; + } + mCurFrame = frame; + selectDrawable(frame); + if (unschedule) { + unscheduleSelf(this); + } + if (animate) { + // Unscheduling may have clobbered this value; restore it to record that we're animating + mCurFrame = frame; + scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); + } + } + +// @Override +/* public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + + TypedArray a = r.obtainAttributes(attrs, + com.android.internal.R.styleable.AnimationDrawable); + + super.inflateWithAttributes(r, parser, a, + com.android.internal.R.styleable.AnimationDrawable_visible); + + mAnimationState.setVariablePadding(a.getBoolean( + com.android.internal.R.styleable.AnimationDrawable_variablePadding, false)); + + mAnimationState.mOneShot = a.getBoolean( + com.android.internal.R.styleable.AnimationDrawable_oneshot, false); + + a.recycle(); + + 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; + } + + a = r.obtainAttributes(attrs, com.android.internal.R.styleable.AnimationDrawableItem); + int duration = a.getInt( + com.android.internal.R.styleable.AnimationDrawableItem_duration, -1); + if (duration < 0) { + throw new XmlPullParserException( + parser.getPositionDescription() + ": tag requires a 'duration' attribute"); + } + int drawableRes = a.getResourceId( + com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0); + + a.recycle(); + + Drawable dr; + if (drawableRes != 0) { + dr = r.getDrawable(drawableRes); + } else { + while ((type = parser.next()) == XmlPullParser.TEXT) { + // Empty + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException(parser.getPositionDescription() + + ": tag requires a 'drawable' attribute or child tag" + + + " defining a drawable"); + } + dr = Drawable.createFromXmlInner(r, parser, attrs); + } + + mAnimationState.addFrame(dr, duration); + if (dr != null) { + dr.setCallback(this); + } + } + + setFrame(0, true, false); + } +*/ + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mAnimationState.mDurations = mAnimationState.mDurations.clone(); + mMutated = true; + } + return this; + } + + private final static class AnimationState extends DrawableContainerState { + private int[] mDurations; + private boolean mOneShot; + + AnimationState(AnimationState orig, AnimationDrawable owner, + Resources res) { + super(orig, owner, res); + + if (orig != null) { + mDurations = orig.mDurations; + mOneShot = orig.mOneShot; + } else { + mDurations = new int[getCapacity()]; + mOneShot = true; + } + } + +// @Override + public Drawable newDrawable() { + return new AnimationDrawable(this, null); + } + +// @Override + public Drawable newDrawable(Resources res) { + return new AnimationDrawable(this, res); + } + + public void addFrame(Drawable dr, int dur) { + // Do not combine the following. The array index must be evaluated before + // the array is accessed because super.addChild(dr) has a side effect on mDurations. + int pos = super.addChild(dr); + mDurations[pos] = dur; + } + + @Override + public void growArray(int oldSize, int newSize) { + super.growArray(oldSize, newSize); + int[] newDurations = new int[newSize]; + System.arraycopy(mDurations, 0, newDurations, 0, oldSize); + mDurations = newDurations; + } + } + + private AnimationDrawable(AnimationState state, Resources res) { + AnimationState as = new AnimationState(state, this, res); + mAnimationState = as; + setConstantState(as); + if (state != null) { + setFrame(0, true, false); + } + } } diff --git a/src/api-impl/android/graphics/drawable/Drawable.java b/src/api-impl/android/graphics/drawable/Drawable.java index d3c23639..8d4173af 100644 --- a/src/api-impl/android/graphics/drawable/Drawable.java +++ b/src/api-impl/android/graphics/drawable/Drawable.java @@ -22,12 +22,18 @@ import android.util.AttributeSet; import android.util.TypedValue; public class Drawable { - public static interface Callback {} + public static interface Callback { + public void invalidateDrawable(Drawable drawable); + public void scheduleDrawable(Drawable drawable, Runnable runnable, long time); + public void unscheduleDrawable(Drawable drawable, Runnable runnable); + } private Rect mBounds = new Rect(); private int[] mStateSet = new int[0]; public long paintable; + private Callback callback = null; + public Drawable() { this.paintable = native_constructor(); } @@ -75,9 +81,28 @@ public class Drawable { return mStateSet; } - public void invalidateSelf() {} + public void setCallback(Callback callback) { + this.callback = callback; + } - public void setCallback(Callback callback) {} + public void invalidateSelf() { + /* this shouldn't ever be needed with Gtk, but let's play it safe for now */ + if (this.callback != null) { + callback.invalidateDrawable(this); + } + } + + public void scheduleSelf(Runnable runnable, long time) { + if (this.callback != null) { + callback.scheduleDrawable(this, runnable, time); + } + } + + public void unscheduleSelf(Runnable runnable) { + if (this.callback != null) { + callback.unscheduleDrawable(this, runnable); + } + } public boolean isVisible() { return false; diff --git a/src/api-impl/android/graphics/drawable/DrawableContainer.java b/src/api-impl/android/graphics/drawable/DrawableContainer.java index e48241cc..be36a85f 100644 --- a/src/api-impl/android/graphics/drawable/DrawableContainer.java +++ b/src/api-impl/android/graphics/drawable/DrawableContainer.java @@ -18,6 +18,7 @@ public class DrawableContainer extends Drawable { if (idx >= 0 && idx < state.childCount && idx != curIndex && state.drawables[idx] != null) { curIndex = idx; native_selectChild(paintable, state.drawables[idx].paintable); + invalidateSelf(); return true; } return false; @@ -34,7 +35,7 @@ public class DrawableContainer extends Drawable { public DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { } - + public int getCapacity() { return drawables.length; } @@ -43,6 +44,10 @@ public class DrawableContainer extends Drawable { return childCount; } + public Drawable getChild(int idx) { + return drawables[idx]; + } + public int addChild(Drawable dr) { if (childCount >= drawables.length) { growArray(drawables.length, drawables.length * 2); @@ -57,5 +62,5 @@ public class DrawableContainer extends Drawable { drawables = newDrawables; } } - + } diff --git a/src/api-impl/android/graphics/drawable/GradientDrawable.java b/src/api-impl/android/graphics/drawable/GradientDrawable.java index 35591194..c2e98911 100644 --- a/src/api-impl/android/graphics/drawable/GradientDrawable.java +++ b/src/api-impl/android/graphics/drawable/GradientDrawable.java @@ -7,5 +7,4 @@ public class GradientDrawable extends Drawable { public void setCornerRadius(float cornerRadius) {} public void setShape(int shape) {} - } diff --git a/src/api-impl/android/view/View.java b/src/api-impl/android/view/View.java index e3fdee64..a8477011 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -16,6 +16,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Parcelable; +import android.os.SystemClock; import android.os.Vibrator; import android.util.AttributeSet; import android.util.LayoutDirection; @@ -29,7 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -public class View extends Object { +public class View implements Drawable.Callback { // --- constants from android source @@ -1007,6 +1008,18 @@ public class View extends Object { protected void onFinishInflate() {} + public void invalidateDrawable(Drawable drawable) { + nativeInvalidate(widget); + } + + public void scheduleDrawable(Drawable drawable, Runnable runnable, long time) { + postDelayed(runnable, time - SystemClock.uptimeMillis()); + } + + public void unscheduleDrawable(Drawable drawable, Runnable runnable) { + /* TODO */ + } + public void invalidate(Rect dirty) { nativeInvalidate(widget); } @@ -1271,6 +1284,8 @@ public class View extends Object { public void setBackgroundDrawable(Drawable backgroundDrawable) { this.background = backgroundDrawable; + if(backgroundDrawable != null) + backgroundDrawable.setCallback(this); native_setBackgroundDrawable(widget, backgroundDrawable != null ? backgroundDrawable.paintable : 0); } diff --git a/src/api-impl/android/widget/ImageView.java b/src/api-impl/android/widget/ImageView.java index 1d9e229b..5c1ca0f6 100644 --- a/src/api-impl/android/widget/ImageView.java +++ b/src/api-impl/android/widget/ImageView.java @@ -73,6 +73,7 @@ public class ImageView extends View { if (drawable instanceof BitmapDrawable) { setImageBitmap(((BitmapDrawable) drawable).getBitmap()); } else if (drawable != null && drawable.paintable != 0) { + drawable.setCallback(this); native_setDrawable(widget, drawable.paintable); } }