项目有个需求:对GIF动图实时添加文字。
结合之前FollowMe景区导游助手项目中做过图片添加水印,MovieMaker项目中多张图片合成一段具有多种切换效果视频的经验,预测需要对GIF文件的数据进行解码,合成处理,编码输出合成文件。
项目基于Glide库实现GIF图片的显示,因此需要搞清楚GIF文件的数据是怎么显示到ImageView模块。本文记录分析GifDrawable模块的原理的过程。
问题:
猜测:
如果是自定义View中的图片需要动起来,则需要主线程每隔一定时间在Canvas上画不同的Bitmap。
以下基于2016/11/01 master版本代码。
GifDrawable
GifState
BitmapPool
GifFrameLoader
Object
Drawable
GifDrawable
GifDrawable本质是个Drawable,属于View中的子模块/底层模块。其中View和Drawable的层次关系:
View--------------------控件树中一个节点,类似一个框
Drawable----------------可以画内容,类似框中的内容
ConstantState-----------多个Drawable可能共享的一段数据
另外GifDrawable还实现了两个接口:
public class GifDrawable extends Drawable implements GifFrameLoader.FrameCallback,
Animatable
其中FrameCallback接口是底层的回调,也就是底层每解码出来一帧就会给上层一个消息,采用回调来解耦:
public interface FrameCallback {
void onFrameReady();
}
再开GifDrawable在接收到底层回调的时候运行什么代码:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onFrameReady() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && getCallback() == null) {
stop();
invalidateSelf();
return;
}
//把自己的矩形区域置为失效,触发下一次View的draw
invalidateSelf();
if (getFrameIndex() == getFrameCount() - 1) {
loopCount++;
}
if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {
stop();
}
}
既然GifDrawable是个可以画的模块,那它到底画什么?
@Override
public void draw(Canvas canvas) {
if (isRecycled) {
return;
}
if (applyGravity) {
Gravity.apply(GifState.GRAVITY, getIntrinsicWidth(), getIntrinsicHeight(), getBounds(),
getDestRect());
applyGravity = false;
}
Bitmap currentFrame = state.frameLoader.getCurrentFrame();
//每次在canvas上画当前帧的Bitmap
canvas.drawBitmap(currentFrame, null, getDestRect(), getPaint());
}
对于Animate接口的实现:
private void startRunning() {
Preconditions.checkArgument(!isRecycled, "You cannot start a recycled Drawable. Ensure that"
+ "you clear any references to the Drawable when clearing the corresponding request.");
// If we have only a single frame, we don't want to decode it endlessly.
if (state.frameLoader.getFrameCount() == 1) {
invalidateSelf();
} else if (!isRunning) {
isRunning = true;
//底层FrameLoader开始工作,并且持有该GifDrawable的引用。用于给onFrameReady的回调。
state.frameLoader.subscribe(this);
invalidateSelf();
}
}
下层subscribe接口的实现:
//上层向下层注册/订阅回调
void subscribe(FrameCallback frameCallback) {
if (isCleared) {
throw new IllegalStateException("Cannot subscribe to a cleared frame loader");
}
boolean start = callbacks.isEmpty();
if (callbacks.contains(frameCallback)) {
throw new IllegalStateException("Cannot subscribe twice in a row");
}
//把上层的多个回调放到一个容器中装起来,以便之后一个个告诉上层
callbacks.add(frameCallback);
if (start) {
//启动该模块
start();
}
}
需要注意的点:
- 该模块自身内部数据结构包含哪些数据,也就是一块内存,可以通过debug模式看动态内存
- 该模块上层调用者是View,依赖的下层是FrameLoader
- 模块的启动、停止过程干了什么事情
- 内部消息/数据流向是怎么样的
- 涉及到哪些线程,每个API/每行代码是运行在哪个线程中,是否需要加锁
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。