Android-Apng动画的播放,大牛手把手动态教学,你都不愿意看吗?

比较:

1)方案一是将Apng文件全部解压成png序列图片保存在本地,方案二是把Apng文件当做一个整体去处理,需要第几帧直接读取第几帧,并将该帧以Bitmap的形似保存到内存。

2)方案一解压得到的png图片在后面的渲染中需要转化成Bitamp,而方案二直接就获取了第几帧的Bitmap,相比于方案一,方案二减少了一个从SD卡读取png文件的操作。

4. Apng的渲染

方案一的具体实现大家可以参考github上面的一个项目apng-view,下面我们来讲讲方案二的具体实现,即ApngReader的具体实现。

  1. 解析Apng的每一帧我们是将整个文件放到一个buffer里面,并且通过RandomAccessFile、MappedByteBuffer来读取Apng的每一帧,ApngReader的构造函数如下:
public ApngReader(String apngFile) throws IOException, FormatNotSupportException {
RandomAccessFile f = new RandomAccessFile(apngFile, "r");
mBuffer = f.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f.length());
f.close();
if (mBuffer.getInt() != PNG_SIG
&& mBuffer.getInt(4) != PNG_SIG_VER
&& mBuffer.getInt(8) != CODE_IHDR) {
throw new FormatNotSupportException("Not a png/apng file");
}
mChunk = new ApngMmapParserChunk(mBuffer);
reset();
}


下面来看看读取每一帧的方法:

/**
* get next frame control info & bitmap
*
* @return next frame control info, or null if no next FCTL chunk || no next IDAT/FDAT
* @throws IOException
*/
public ApngFrame nextFrame() throws IOException {
// reset read pointers from previous frame's lock
mPngStream.clearDataChunks();
mPngStream.resetPos();
mChunk.unlockRead();

// locate next FCTL chunk
boolean ihdrCopied = false;
while (mChunk.typeCode != CODE_fcTL) {
switch (mChunk.typeCode) {
case CODE_IEND:
return null;
case CODE_IHDR:
mPngStream.setIHDR(mChunk.duplicateData());
break;
case CODE_acTL:
handleACTL(mChunk);
ihdrCopied = true;
break;
default:
handleOtherChunk(mChunk);
}
mChunk.parseNext();
}

// located at FCTL chunk
ApngFrame frame = new ApngFrame();
mChunk.assignTo(frame);

// locate next IDAT or fdAt chunk
mChunk.parseNext();// first move next from current FCTL
while (mChunk.typeCode != CODE_IDAT && mChunk.typeCode != CODE_fdAT) {
switch (mChunk.typeCode) {
case CODE_IEND:
return null;
case CODE_IHDR:
mPngStream.setIHDR(mChunk.duplicateData());
ihdrCopied = true;
break;
case CODE_acTL:
handleACTL(mChunk);
break;
default:
handleOtherChunk(mChunk);
}
mChunk.parseNext();
}

// located at first IDAT or fdAT chunk
// collect all consecutive dat chunks
boolean needUpdateIHDR = true;
int dataOffset = mChunk.getOffset();
while (mChunk.typeCode == CODE_fdAT || mChunk.typeCode == CODE_IDAT) {
if (needUpdateIHDR && (!ihdrCopied || mChunk.typeCode == CODE_fdAT)) {
mPngStream.updateIHDR(frame.getWidth(), frame.getHeight());
needUpdateIHDR = false;
}

if (mChunk.typeCode == CODE_fdAT) {
mPngStream.addDataChunk(new Fdat2IdatChunk(mChunk));
} else {
mPngStream.addDataChunk(new ApngMmapParserChunk(mChunk));
}
mChunk.parseNext();
}

// lock position for this frame's image as OutputStream
mChunk.lockRead(dataOffset);
frame.imageStream = mPngStream;
return frame;
}


  1. Apng的消除操作Apng的消除操作是在ApngFrameRender的render方法做的,方法如下:
/**
* 渲染当前帧画面
*
* @param frame apng中当前帧
* @return 渲染合成后的当前帧图像
*/
public Bitmap render(ApngFrame frame, Bitmap frameBmp) {
// 执行消除操作
dispose(frame);
// 合成当前帧
blend(frame, frameBmp);
return mRenderFrame;
}


dispose(ApngFrame frame)方法如下:

/**
* 帧图像析构消除 - 提交结果
*/
private void dispose(ApngFrame frame) {
// last frame dispose op
switch (mLastDisposeOp) {
case APNG_DISPOSE_OP_NONE:
// no op
break;

case APNG_DISPOSE_OP_BACKGROUND:
// clear rect
mRenderCanvas.clipRect(mDisposeRect);
mRenderCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mRenderCanvas.clipRect(mFullRect, Region.Op.REPLACE);
break;

case APNG_DISPOSE_OP_PREVIOUS:
// swap work and cache bitmap
Bitmap bmp = mRenderFrame;
mRenderFrame = mDisposedFrame;
mDisposedFrame = bmp;
mRenderCanvas.setBitmap(mRenderFrame);
mDisposeCanvas.setBitmap(mDisposedFrame);
break;
}

// current frame dispose op
mLastDisposeOp = frame.getDisposeOp();
switch (mLastDisposeOp) {
case APNG_DISPOSE_OP_NONE:
// no op
break;

case APNG_DISPOSE_OP_BACKGROUND:
// cache rect for next clear dispose
int x = frame.getxOff();
int y = frame.getyOff();
mDisposeRect.set(x, y, x + frame.getWidth(), y + frame.getHeight());
break;

case APNG_DISPOSE_OP_PREVIOUS:
// cache bmp for next restore dispose
mDisposeCanvas.clipRect(mFullRect, Region.Op.REPLACE);
mDisposeCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mDisposeCanvas.drawBitmap(mRenderFrame, 0, 0, null);
break;
}
}


  1. Apng的合成操作Apng的合成操作是在每一帧经过dispose之后做的,具体方法是blend(ApngFrame frame, Bitmap frameBmp),代码如下:
/**
* 帧图像合成
*/
private void blend(ApngFrame frame, Bitmap frameBmp) {
int xOff = frame.getxOff();
int yOff = frame.getyOff();

mRenderCanvas.clipRect(xOff, yOff, xOff + frame.getWidth(), yOff + frame.getHeight());
if (frame.getBlendOp() == APNG_BLEND_OP_SOURCE) {
mRenderCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
}
mRenderCanvas.drawBitmap(frameBmp, xOff, yOff, null);
mRenderCanvas.clipRect(mFullRect, Region.Op.REPLACE);
}


  1. Apng的绘制Apng的每一帧经过消除、合成操作之后,就可以在View上面draw,具体代码如下:
/**
* draw the appointed frame
*/
private void drawFrame(AnimParams animItem, ApngFrame frame, Bitmap frameBmp) {
if (surfaceEnabled && !isInterrupted()) {
//start to draw the frame
try {
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
Bitmap bmp = mFrameRender.render(frame, frameBmp);

//saveBitmap(bmp, index);
index ++;

Canvas canvas = getHolder().lockCanvas();
//anti-aliasing
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
float[] tranLeftAndTop = ApngUtils.getTranLeftAndTop(canvas, bmp, animItem.align, mScale, animItem.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);
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/fcb0138a518ff7920f7749b3b99d16a6.png)

![img](https://img-blog.csdnimg.cn/img_convert/8ac77834f2d9e0c1520c9d4be632bacf.png)

![img](https://img-blog.csdnimg.cn/img_convert/fd4ee47b669bc726ab494608d8a3ba2c.png)

![img](https://img-blog.csdnimg.cn/img_convert/b02d1dffdcf779578b1ed15135a9f823.png)

![](https://img-blog.csdnimg.cn/img_convert/f548e21c6a1e06fa3e58603a8dd0bcc2.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

**如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)**

![](https://img-blog.csdnimg.cn/img_convert/4ef8a6a1e475ff5b12b15bade7323506.jpeg)



## 学习福利

**【Android 详细知识点思维脑图(技能树)】**

> ![](https://img-blog.csdnimg.cn/img_convert/7bceff11c53b8915e040c421ef2016be.webp?x-oss-process=image/format,png)

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,**现在高级工程师还是比较缺少的**,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

> 这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

![](https://img-blog.csdnimg.cn/img_convert/f8bee8981342b154f930056979c4dd96.webp?x-oss-process=image/format,png)

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。


**《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》[点击传送门即可获取!](https://bbs.csdn.net/forums/f76c2498e3b04ae99081eaf6e6cf692c)**

细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中...(img-ZMRAsz4i-1713773656273)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。


**《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》[点击传送门即可获取!](https://bbs.csdn.net/forums/f76c2498e3b04ae99081eaf6e6cf692c)**

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值