1. 概述
在深入了解Apng动画播放之前,我们需要对Apng的结构有所了解,具体参见Apng动画介绍。对Apng的整体结构有所了解后,下面我们来讲讲Apng动画的播放,主要包括Apng解析和Apng渲染两个过程,这个参见Android-Apng动画的播放。下面我们讲讲ApngImageView是怎么实现Apng的动画播放的,先看一下效果图。
2. ApngImageView的实现
ApngImageView的实现主要是靠ApngPlayAssist类来实现的,ApngPlayAssist是实现了Runnable接口,主要做了以下三个事情:
public void run() {
if (mAnimParams == null) {
return;
}
Log.d(TAG, "PlayThread run()");
try {
// play it
playAnimation();
// play end
stop();
} catch (InterruptedException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
mFrameRender.recycle();
}
}
1)在子线程里面,通过ApngFrameRender和ApngReader读取Apng的每一帧。
说明:playAnimation主要是读取每一帧,然后通知ImageView去绘制每一帧。
private void playAnimation() throws InterruptedException {
try {
mFrameRender = new ApngFrameRender();
// step 1: prepare
ApngReader reader = new ApngReader(mAnimParams.imagePath);
ApngACTLChunk actl = reader.getACTL();
if (mAnimParams.isHasBackground) setBgColor(true);
// all loop count = apng_internal_loop_count x apng_play_times
// if apng_internal_loop_count == 0 then set it to 1 (not support loop indefinitely)
int loopCount = mAnimParams.loopCount * (actl.getNumPlays() == 0 ? 1 : actl.getNumPlays());
// step 2: draw frames
boolean isLoop = loopCount == AnimParams.PLAY_4_LOOP;
for (int lc = 0; lc < loopCount || isLoop; lc++) {
// reallocated to head again if loops more the one time
if (lc > 0 || isLoop) reader.reset();
for (int i = 0; i < actl.getNumFrames(); i++) {
long start = System.currentTimeMillis();
// get frame data
curFrame = reader.nextFrame();
if (curFrame == null) break; // if read next frame failed, break loop
byte[] data = readStream(curFrame.getImageStream());
if (data != null) {
//Bitmap frameBmp = BitmapFactory.decodeStream(frame.getImageStream());
curFrameBmp = BitmapFactory.decodeByteArray(data, 0, data.length);
Log.d(TAG, "read the " + i + " frame:" + (System.currentTimeMillis() - start) + "ms");
// init the render and calculate scale rate
// at first time get the frame width and height
if (lc == 0 && i == 0) {
int imgW = curFrame.getWidth(), imgH = curFrame.getHeight();
mScale = calculateScale(mAnimParams.scaleType, imgW, imgH, getWidth(), getHeight());
mFrameRender.prepare(imgW, imgH);
}
index++;
mStep = ApngLoader.Const.STEP_DRAW_FRAME;
ApngImageView.this.postInvalidate();
// delay
int waitMillis = Math.round(curFrame.getDelayNum() * DELAY_FACTOR / curFrame.getDelayDen())
- (int) (System.currentTimeMillis() - start);
Thread.sleep(waitMillis > 0 ? waitMillis : 0);
}
}
}
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
if (mAnimParams.isHasBackground) setBgColor(false);
}
}
2)把读取到Apng的每一帧在Canvas里绘制。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (mStep){
case ApngLoader.Const.STEP_CLEAR_CANVAS:
mApngPlayAssist.clearCanvas(canvas);
break;
case ApngLoader.Const.STEP_DRAW_FRAME:
mApngPlayAssist.drawFrame(canvas);
break;
default:
break;
}
}
private void drawFrame(Canvas canvas) {
if (mIsPlay
&& mFrameRender != null
&& curFrame != null
&& curFrameBmp != null) {
//start to draw the frame
try {
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
Bitmap bmp = mFrameRender.render(curFrame, curFrameBmp);
//saveBitmap(bmp, index);
//anti-aliasing
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
float[] tranLeftAndTop = ApngUtils.getTranLeftAndTop(canvas, bmp, mAnimParams.align, mScale, mAnimParams.percent);
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
matrix.postTranslate(tranLeftAndTop[0], tranLeftAndTop[1]);
canvas.drawBitmap(bmp, matrix, null);
curFrameBmp.recycle();
} catch (Exception e) {
Log.e(TAG, "draw error msg:" + Log.getStackTraceString(e));
}
}
}
3)动画播放结束,给业务层回调。
private void stop() {
mIsPlay = false;
mStep = ApngLoader.Const.STEP_CLEAR_CANVAS;
notifyPlayCompeleted();
}
3. 使用方法
这里我们写了一个ApngLoader,使用方法如下:
private void playAnim(){
//File file = FileUtils.processApngFile(COLOR_BALL_IMAGE_PATH, this);
File file1 = FileUtils.processApngFile(COLOR_BALL_IMAGE_PATH, this);
ApngLoader.getInstance().loadApng(file1.getAbsolutePath(), mApngImageView);
}
4. 说明
上面的一些说明可能比较简单,具体的实现,大家可以去下面的项目地址下载:
ApngImageView源码:ApngImageView。
SakuApng地址:Apng的项目地址-SakuApng。
例子演示: