# Drawable Animation
## 幀動畫使用
### animalist.xml
```
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@mipmap/c_1"
android:duration="50" />
<item
android:drawable="@mipmap/c_2"
android:duration="50" />
<!-- 省略... -->
<item
android:drawable="@mipmap/circle_19"
android:duration="50" />
<item
android:drawable="@mipmap/circle_20"
android:duration="50" />
</animation-list>
```
### layout.xml
```
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ansen.frameanimation.sample.MainActivity">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/animlist" />
</LinearLayout>
```
### java類使用
```
ImageView image = (ImageView) findViewById(R.id.image);
AnimationDrawable animationDrawable = (AnimationDrawable) image.getDrawable();
animationDrawable.start();
```
## DrawableAnimation流程圖

## 幀動畫代碼執行過程
### start
```
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
/***代碼部分省略***/
@Override
public void start() {
mAnimating = true;
if (!isRunning()) {
// Start from 0th frame.
setFrame(0, false, mAnimationState.getChildCount() > 1
|| !mAnimationState.mOneShot);
}
}
//設置當前展示第幾幀
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]);
}
}
//安排動畫繪制任務
public void scheduleSelf(Runnable what, long when) {
//該Callback是當前AnimationDrawable綁定的View
final Callback callback = getCallback();
//判斷當前綁定的View是否被銷毀
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
}
```
### scheduleDrawable
- [ViewRootImpl的獨白,我不是一個View(布局篇)](https://blog.csdn.net/stven_king/article/details/78775166)
- [Android系統的編舞者Choreographer](https://blog.csdn.net/stven_king/article/details/80098845)
```
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
/***部分代碼省略***/
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (verifyDrawable(who) && what != null) {
final long delay = when - SystemClock.uptimeMillis();
if (mAttachInfo != null) {
//請求Vsync信號同步
mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
Choreographer.CALLBACK_ANIMATION, what, who,
Choreographer.subtractFrameDelay(delay));
} else {
ViewRootImpl.getRunQueue().postDelayed(what, delay);
}
}
}
}
```
### run
```
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
/***代碼部分省略***/
//Choreographer的Vsync同步回調
@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);
}
}
```
## 其他
### CallBack的綁定
```
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
/***部分代碼省略***/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
/***部分代碼省略***/
//清除之前的背景
if (mBackground != null) {
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
if (background != null) {
/***部分代碼省略***/
//Drawable綁定當前的View
background.setCallback(this);
if (background.isStateful()) {
background.setState(getDrawableState());
}
background.setVisible(getVisibility() == VISIBLE, false);
mBackground = background;
applyBackgroundTint();
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
} else {
/***部分代碼省略***/
}
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
invalidate(true);
}
}
```
### 內存方面
幀動畫相比較屬性動畫而言可能會出現OOM,因為在家的每一幀的圖片會占用很大的內存空間。
幀動畫不會出現內存泄露的問題:
```
public abstract class Drawable {
/***部分代碼省略***/
//持有當前View的弱引用,當View回收之后,沒辦法繼續下一幀的展示
private WeakReference<Callback> mCallback = null;
public Callback getCallback() {
if (mCallback != null) {
return mCallback.get();
}
return null;
}
}
```
- 寫在前面的話
- Java
- 基礎
- Double的比較
- 小數怎么用二進制表示
- 多線程
- 并發和并行
- 線程池
- 線程池背景
- 線程池構造
- 任務阻塞隊列
- Flutter
- 基礎知識
- Dart基礎
- Android
- 項目架構
- View
- 非UI線程更新View
- AlarmManager
- 對比postDelaryed和Timer
- Bitmap
- 加載100M的圖片卻不撐爆內存
- Bitmap壓縮
- Bitmap局部解碼
- 計算圖片的內存占用
- Android動畫
- Android動畫類型
- Android動畫原理
- 屬性動畫
- 幀動畫
- 補間動畫
- 使用動畫的注意事項
- Android新特性
- 權限組
- Android23(Marshmallow)-6.0
- Android24(Nougat)-7.0
- Android26(Oreo)-8.0
- Android28(Pie)-9.0
- Android29(Q)-10.0
- AndroidX遷移
- Kotlin
- 關鍵字
- Kotlin操作符
- CoroutineScope
- Flow
- CoroutineException