主要分析下动画是如何实现的。
XML解析转换成AnimationDrawable这块请自行查看源码。
先看下使用方式
AnimationDrawable drawable = new AnimationDrawable();
for (int i = 1; i <= 9; i++) {
int resId = getResources().getIdentifier("loading_" + df.format(i), "drawable", getActivity().getPackageName());
drawable.addFrame(getResources().getDrawable(resId, null), 100);
img.setImageDrawable(drawable);
}
drawable.start();
首先分析下setImageDrawable方法
/**
* Sets a drawable as the content of this ImageView.
*
* @param drawable the Drawable to set, or {@code null} to clear the
* content
*/
public void setImageDrawable(@Nullable Drawable drawable) {
if (mDrawable != drawable) {
......
updateDrawable(drawable);
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
private void updateDrawable(Drawable d) {
......
mDrawable = d;
if (d != null) {
// 将当前View设置给了Drawable
d.setCallback(this);
......
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
接下来分析下AnimationDrawable
AnimationDrawable Drawable孙子类,继承DrawableContainer。 AnimationState 继承DrawableContainerState,扩展维护Duration,和OneShot。 DrawableContainerState 维护所有图片
/**
* 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);
}
}
- 添加图片
- 设置当前默认的图片。animate为false,即默认不启动动画
/**
* Starts the animation from the first frame, looping if necessary. This method has no effect
* if the animation is running.
* <p>
* <strong>Note:</strong> 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);
}
}
- 注释,不能在Activity.onCreate方法调用start()。可以移植到onWindowFocusChanged方法中。
- 调用setFrame,并启动动画
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]);
}
}
/**
* Use the current {@link Callback} implementation to have this Drawable
* scheduled. Does nothing if there is no Callback attached to the
* Drawable.
*
* @param what The action being scheduled.
* @param when The time (in milliseconds) to run.
*
* @see Callback#scheduleDrawable
*/
public void scheduleSelf(@NonNull Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
- AnimationDrawable继承Runnable接口
- getCallback()返回的就是设置的View。所以调用View.scheduleDrawable方法,来进行Drawable的切换。
/**
* Schedules an action on a drawable to occur at a specified time.
*
* @param who the recipient of the action
* @param what the action to run on the drawable
* @param when the time at which the action must occur. Uses the
* {@link SystemClock#uptimeMillis} timebase.
*/
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
if (verifyDrawable(who) && what != null) {
final long delay = when - SystemClock.uptimeMillis();
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
Choreographer.CALLBACK_ANIMATION, what, who,
Choreographer.subtractFrameDelay(delay));
} else {
// Postpone the runnable until we know
// on which thread it needs to run.
getRunQueue().postDelayed(what, delay);
}
}
}
主要处理定时切换功能。最后会执行AnimationDrawable.run()方法。
/**
* 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);
}
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);
}
这样就实现了图片的切换效果。
那么图片是如何绘制出来的呢。 setFrame()方法中调用了selectDrawable(frame);
/**
* Sets the currently displayed drawable by index.
* <p>
* If an invalid index is specified, the current drawable will be set to
* {@code null} and the index will be set to {@code -1}.
*
* @param index the index of the drawable to display
* @return {@code true} if the drawable changed, {@code false} otherwise
*/
public boolean selectDrawable(int index) {
......
if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(index);
mCurrDrawable = d;
mCurIndex = index;
if (d != null) {
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
}
initializeDrawableForDisplay(d);
}
} else {
mCurrDrawable = null;
mCurIndex = -1;
}
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
animate(true);
invalidateSelf();
}
};
} else {
unscheduleSelf(mAnimationRunnable);
}
// Compute first frame and schedule next animation.
animate(true);
}
invalidateSelf();
return true;
}
/**
* Use the current {@link Callback} implementation to have this Drawable
* redrawn. Does nothing if there is no Callback attached to the
* Drawable.
*
* @see Callback#invalidateDrawable
* @see #getCallback()
* @see #setCallback(android.graphics.drawable.Drawable.Callback)
*/
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
- 设置图片Drawable对象
- 调用invalidateSelf 重绘,前面分析,callback其实就是View
/**
* Invalidates the specified Drawable.
*
* @param drawable the drawable to invalidate
*/
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
rebuildOutline();
}
}
调用View.invalidate(),触发onDraw()方法。具体有子类执行。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable == null) {
return; // couldn't resolve the URI
}
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return; // nothing to draw (empty bounds)
}
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
final int saveCount = canvas.getSaveCount();
canvas.save();
if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
}
canvas.translate(mPaddingLeft, mPaddingTop);
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
最后通过调用Drawable.draw()来实现具体绘制图片。