# 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流程图
![](../images/DrawableAnimation流程图.png)
## 帧动画代码执行过程
### 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