前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >在Android中显示APNG动图

在Android中显示APNG动图

原创
作者头像
Clayman Twinkle
发布于 2019-04-04 10:16:46
发布于 2019-04-04 10:16:46
17.6K1
举报
文章被收录于专栏:Android原创Android原创

一、什么是APNG?

APNG(Animated Portable Network Graphics)是一个基于PNG(Portable Network Graphics)的位图动画格式,用途类似GIF,其诞生的目的是为了替代老旧的 GIF 格式。

二、与GIF对比

说了这么多,它替代GIF?那有什么优势呢?

总结下来有以下几点:

(1)GIF最多支持 8 位 256 色,而APNG支持24 位真彩色和alpha通道,不会出现像GIF的锯齿;

(2)APNG图通过优化,图片大小和GIF差不多,甚至小一点。

三、在Android中显示APNG动图

这里使用了一个开源库来解析加载APNG图,apng-view

使用示例:

代码语言:txt
AI代码解释
复制
String url = "http://xxx.png";
imageView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ApngDrawable apngDrawable = ApngDrawable.getFromView(v);
        if (apngDrawable == null) return;
        if (apngDrawable.isRunning()) {
            apngDrawable.stop();  // 停止播放动画
        } else {
            apngDrawable.setNumPlays(3); // 动画循环次数
            apngDrawable.start(); // 开始播放动画
        }
    }
});

ApngImageLoader.getInstance().displayImage(url, imageView);

效果图:

四、apng-view源码分析

实现过程

先看看apng-view实现过程:

实现过程
实现过程

(1)图片的下载/加载:通过图片加载开源库Android-Universal-Image-Loader进行图片的下载/加载;

(2)通过下载成功后的图片文件构造ApngDrawable对象;

(3)最后通过imageView.setImageDrawableApngDrawableImageView绑定到一起;

所以,这个apng-view库中,最核心的就是ApngDrawable这个类了。

那么这个ApngDrawable里面究竟做了什么骚操作呢?

源码解读

(1)prepare

先从图片文件读取这里说起,图片读取是在ApngDrawable这个prepare()方法中进行的;

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/ApngDrawable.java
private void prepare() {
 	// 1. 构造File对象
	String imagePath = getImagePathFromUri();
	if (imagePath == null) return;
	baseFile = new File(imagePath);
	if (!baseFile.exists()) return;
	// 2. 读取一个APNG文件并尝试将其拆分帧
	ApngExtractFrames.process(baseFile);
	// 3. 读取APNG文件信息
	readApngInformation(baseFile);
	isPrepared = true;
}

代码的步骤,说得云里雾里的,且看这个ApngExtractFrames.process()方法里具体实现吧;

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/assist/ApngExtractFrames.java
public static int process(final File orig) {
	PngReaderBuffered pngr = new PngReaderBuffered(orig); // 这里应该是在读取了这个图片
	pngr.end();
	return pngr.frameIndex + 1;
}

这里用到了一个可以用来读取PNG的开源库pngj,大概知道这是在读图片了,读的过程中做了什么操作呢?

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/assist/ApngExtractFrames.java
protected void postProcessChunk(ChunkReader chunkR) {
	//......
    try {
        String id = chunkR.getChunkRaw().id;
        PngChunk lastChunk = chunksList.getChunks().get(chunksList.getChunks().size() - 1);
        if (id.equals(PngChunkFCTL.ID)) { // FCTL代表,每一帧开头
            frameIndex++;
            frameInfo = ((PngChunkFCTL) lastChunk).getEquivImageInfo();
            startNewFile(); // 开始新建一个文件,进行输入
        }
        if (id.equals(PngChunkFDAT.ID) || id.equals(PngChunkIDAT.ID)) { // 图像数据块
            // 忽略这里的处理细节....
        }
        if (id.equals(PngChunkIEND.ID)) { // 这一帧结束
            if (fo != null)
                endFile(); // 结束这个文件输入,对应startNewFile方法
        }
    } catch (Exception e) {
        throw new PngjException(e);
    }
}

大概逻辑就是将APNG图片读取后,拆解生成多个帧文件,存放起来;

接下来看下ApngDrawable#prepare()中步骤三readApngInformation具体做了什么吧;

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/ApngDrawable.java
private void readApngInformation(File baseFile) {
	PngReaderApng reader = new PngReaderApng(baseFile); // 又读取了一次文件,这里和步骤二或许可以合并优化下
	reader.end();

	List<PngChunk> pngChunks = reader.getChunksList().getChunks(); // 拿到图片所有数据块
	PngChunk chunk;

	for (int i = 0; i < pngChunks.size(); i++) {
		chunk = pngChunks.get(i);
		if (chunk instanceof PngChunkACTL) {
			numFrames = ((PngChunkACTL) chunk).getNumFrames();  //获取总帧数
			if (numPlays > 0) {
				//......
			} else {
				numPlays = ((PngChunkACTL) chunk).getNumPlays(); // 获取循环播次数
			}
		} else if (chunk instanceof PngChunkFCTL) {
			fctlArrayList.add((PngChunkFCTL) chunk); // 收集帧动画控制的数据块
		}
	}
}

这个过程大体上就是在解析这个APNG文件的基本信息。

(2)start

那么到了这个动图的start阶段了

代码语言:txt
AI代码解释
复制
// 文件路径:com/github/sahasbhop/apngview/ApngDrawable.java
	public void start() {
		if (!isRunning()) {
			isRunning = true;
			currentFrame = 0;
			if (!isPrepared) {
				prepare();
			}
            if (isPrepared) {
                run();
				if (apngListener != null) apngListener.onAnimationStart(this);
            } else {
                stop();
            }
		}
	}

这个start方法里其实也没做什么,只是通过标志位去判断执行preparerunstop方法而已;

(3)run

动图播放的核心方法之一run

代码语言:txt
AI代码解释
复制
public void run() {
	if (showLastFrameOnStop && numPlays > 0 && currentLoop >= numPlays) {
		stop(); // 轮播次数用完且到最后一帧了就停止播放了
		return;
	}

	if (currentFrame < 0) {
		currentFrame = 0;
	} else if (currentFrame > fctlArrayList.size() - 1) {
		currentFrame = 0; // 因为没轮播完,所以当前帧序号从0开始
	}

	PngChunkFCTL pngChunk = fctlArrayList.get(currentFrame);

	int delayNum = pngChunk.getDelayNum();
	int delayDen = pngChunk.getDelayDen();
	int delay = Math.round(delayNum * DELAY_FACTOR / delayDen);

	scheduleSelf(this, SystemClock.uptimeMillis() + delay); // 定时器,循环走run
	invalidateSelf(); // 通知draw再一次了
}

(4)stop

暂停动图的方法

代码语言:txt
AI代码解释
复制
public void stop() {
	if (isRunning()) {
		currentLoop = 0;
		unscheduleSelf(this); // 停止定时器
		isRunning = false;
		if (apngListener != null) apngListener.onAnimationEnd(this);
	}
}

(5)draw

动图播放的核心方法之二draw

APNG图是怎么给绘制出来的呢?

代码语言:txt
AI代码解释
复制
public void draw(Canvas canvas) {
	if (currentFrame <= 0) { 
		drawBaseBitmap(canvas);
	} else {
		drawAnimateBitmap(canvas, currentFrame);
	}

	if (!showLastFrameOnStop && numPlays > 0 && currentLoop >= numPlays) {
		stop(); // 不轮播了就停止
	}

	if (numPlays > 0 && currentFrame == numFrames - 1) { // 最后一帧了
		currentLoop++; // 循环次数加一
		if (apngListener != null) apngListener.onAnimationRepeat(this);
	}
	currentFrame++;
}

绘制动图的核心代码在drawAnimateBitmap方法里:

代码语言:txt
AI代码解释
复制
private void drawAnimateBitmap(Canvas canvas, int frameIndex) {
	Bitmap bitmap = getCacheBitmap(frameIndex); // 这里对帧bitmap做了缓存
		if (bitmap == null) {
			bitmap = createAnimateBitmap(frameIndex); // 没缓存直接通过帧文件创建bitmap
			cacheBitmap(frameIndex, bitmap); // 缓存!
		}
		if (bitmap == null) return;
		RectF dst = new RectF(0, 0,mScaling * bitmap.getWidth(),mScaling * bitmap.getHeight());
		canvas.drawBitmap(bitmap, null, dst, paint); // 绘制
}

至此,核心代码逻辑大致分析差不多。

总结下来ApngDrawable核心逻辑大致分三步:

(1)APNG拆分成多个帧文件:图片文件通过开源库pngjPngChunk数据结构读到内存,然后遍历数据块,将APNG每一帧数据保存到本地文件中;

(2)读取APNG基本图片信息;

(3)开启定时器逐帧读取文件(读完后缓存一次)生成Bitmap绘制到View上;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
1 条评论
热度
最新
如何从raw中读取apng呢,放在assets里面可以,raw里面不行,但是如果做皮肤的话必须放到raw里面
如何从raw中读取apng呢,放在assets里面可以,raw里面不行,但是如果做皮肤的话必须放到raw里面
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
SVGA源码解析
主要是decodeFromInputStream方法,读取解析来自URL的svga文件,并缓存成本地文件
雁字回时
2022/12/13
7620
Android PowerImageView实现,可以播放动画的强大ImageView
本文介绍了如何在Android开发中使用PowerImageView显示GIF图片,并给出了完整的示例代码。同时,还介绍了如何为PowerImageView设置允许自动播放和循环播放的GIF图片,以及如何使用ImageView显示普通的PNG图片。
用户1158055
2018/01/05
1.6K0
Android PowerImageView实现,可以播放动画的强大ImageView
不一样的动图-APNG
动图 说到动图,首先我们想到的 GIF 格式,GIF 在网络上流行已久,各种动态表情包都是 GIF 图做的。 但是 GIF 的缺点也很明显,透明背景的 GIF 无法做到像素的绝对平滑过度,于是乎我们可以看到带透明的 GIF 图周围杂边非常明显,如下: image.png 通常解决这种问题是在外面加上一圈白色的描边,这样在白色背景下可以掩盖杂边问题,但是在深色背景下依然无解: image.png APNG 完美的解决了这个问题。 APNG APNG 全称是 Animated Portable Netw
Bob.Chen
2018/05/02
7.8K0
不一样的动图-APNG
GIF格式解析
前言 本文参考gif 格式图片详细解析。加入了一些自己的理解和解析方面的示例。 ---- GIF格式解析 图像互换格式(GIF,Graphics Interchange Format)是一种位图图形文件格式,以8位色(即256种颜色)重现真彩色的图像。它实际上是一种压缩文档,采用LZW压缩算法进行编码,有效地减少了图像文件在网络上传输的时间。它是目前广泛应用于网络传输的图像格式之一。 图像互换格式主要分为两个版本,即图像互换格式87a和图像互换格式89a。 图像互换格式87a:是在1987年制定的版本。
Oceanlong
2018/07/03
6.3K0
利用Android系统源码中giflib实现播放gif文件
目前市场上流行的图片框架都是可以很好的处理gif图片,像glide是通过Java层来处理gif的展示,但是Java层来处gif的展示,始终会存在OOM的风险。今天学习了一下Android系统源码中拓展源码的giflib加载gif。
包子388321
2020/07/01
2.1K0
NDK--实现gif图片播放
部分Gif图片不能自适应大小, 播放速度比实际播放速度快, 如果要显示的gif过大,还会出现OOM的问题。
aruba
2020/07/02
1.5K0
iOS 客户端动图优化实践
GIF 和 Animated WebP 是互联网上最主流的动图格式, 但是在 iOS 开发中, 原生的 UIImage 并不直接支持 GIF 以及 Animated WebP 的展示, 因此有了各种优秀的第三方开源方案, 例如 SDWebImage 以及 YYImage 等. 这篇文章将以 QQ 音乐 iOS 端优化动图的实践为基础, 来介绍不同方案的思路以及优劣, 并给出优化的方案. 1. 端内动图展示的问题以及优化结果 长期以来, 部分机型浏览 Q 音的图文流时很容易闪退, 端内其他业务也存在不少动图相
QQ音乐技术团队
2023/05/12
6.1K3
iOS 客户端动图优化实践
大图做帧动画就卡顿?不存在的!
https://juejin.im/post/5cd240f2e51d453afb40d83a
吴延宝
2019/07/24
1.1K0
Android开发笔记(十七)GIF动画的实现GifAnimation
GIF在Windows上是常见的图片格式,主要用来播放短小的动画。但在手机上由于系统资源紧张,所以Android并没有直接支持GIF格式,如果在ImageView中放入一张gif文件,你会发现显示出来的只是该gif文件的第一帧图片。 对于这种情况,Android带来了帧动画技术,通过连续播放每帧图片,从而实现帧动画的效果。不过若要使用帧动画,我们得自己准备好若干帧,然后把这些图片帧编入图片队列,这样才可以显示动画。对于如何从gif文件中提取出每帧图片,博主在之前的文章中有做了说明,详见《Android开发笔记(十)常用的图片加工操作》。 可是手工分解gif文件也太麻烦了,如果gif数量多的话,岂不累坏了。能否通过代码直接从gif文件中提取每帧图片呢?答案是有的,已经有大牛研究出来了,那么我们直接把相关算法拿过来,改改就可以用了。下面是调用的代码例子,为方便比较帧动画和GIF动画的效果,代码同时实现了两种动画
aqi00
2019/01/18
1.2K0
YYImage框架瞧一瞧
建议查看原文:https://www.jianshu.com/p/83edaeeb5851(不定时更新)
Dwyane
2018/09/30
2.2K0
YYImage框架瞧一瞧
android GifView分享
gif图动画在android中还是比较常用的,比如像新浪微博中,有很多gif图片,而且展示非常好,所以我也想弄一个。经过我多方的搜索资料和整理,终于弄出来了,其实github上有很多开源的gif的展示
xiangzhihong
2018/01/30
8910
android GifView分享
一道图片隐写题引发的思考
下载附件,只有一张图片,后缀为png格式,用010editor查看并不能找到什么由出题人写入的额外的信息,而常见的png隐写方式zsteg、LSB,经过我的各种尝试,也无法解出任何信息,一时间想不到该怎么解这道题,在网上也查不到有关该题的wp,于是我点开了solved列表,刚好在里面发现了一个熟悉的名字
回天
2023/04/25
4740
一道图片隐写题引发的思考
安德鲁斯—-多媒体编程
在内存中创建图片的副本 直接载入的bitmap对象是仅仅读的。无法改动。要改动图片仅仅能在内存中创建出一个一模一样的bitmap副本。然后改动副本
全栈程序员站长
2022/07/06
4430
iOS中播放gif动态图的方式探讨 原
    在iOS开发中,UIImageView类专门来负责图片数据的渲染,并且UIImageView也有帧动画的方法来播放一组图片,但是对于gif类型的数据,UIImageView中并没有现成的接口提供给开发者使用,在iOS中一般可以通过两种方式来播放gif动态图,一种方式是通过ImageIO框架中的方法将gif文件中的数据进行解析,再使用coreAnimation核心动画来播放gif动画,另一种方式计较简单,可以直接通过webView来渲染gif图。
珲少
2018/08/15
1.9K0
iOS中播放gif动态图的方式探讨
                                                                            原
Android 面试之必问性能优化
对于Android开发者来说,懂得基本的应用开发技能往往是不够,因为不管是工作还是面试,都需要开发者懂得大量的性能优化,这对提升应用的体验是非常重要的。对于Android开发来说,性能优化主要围绕如下方面展开:启动优化、渲染优化、内存优化、网络优化、卡顿检测与优化、耗电优化、安装包体积优化、安全问题等。
xiangzhihong
2021/07/21
9180
FLAnimatedImage -ios gif图片加载框架介绍
简介 FLAnimatedImage 是 Flipboard 团队开发的在它们 App 中渲染 GIF 图片使用的库。 后来 Flipboard 将 FLAnimatedImage 开源出来供大家使用。本文章主要是介绍FLAnimatedImage框架的GIF动画加载和播放流程,旨在说明流程和主要细节点。 ios原有加载缺陷分析 大家知道在 iOS 中处理过 GIF 图片, 如果通过原生系统提供的能力, 可能只有两种方式。 并且这两种方式都不是专门针对于 GIF 的解决方案,更像是一种 hack。 第一
xiangzhihong
2018/02/05
3.9K0
FLAnimatedImage -ios gif图片加载框架介绍
Android性能优化(一)
一个应用App的启动速度能够影响用户的首次体验,启动速度较慢(感官上)的应用可能导致用户再次开启App的意图下降,或者卸载放弃该应用程序。
xiangzhihong
2021/01/22
2.7K0
FLAnimatedImage -ios gif图片加载框架介绍
简介 FLAnimatedImage 是 Flipboard 团队开发的在它们 App 中渲染 GIF 图片使用的库。 后来 Flipboard 将 FLAnimatedImage 开源出来供大家使用。本文章主要是介绍FLAnimatedImage框架的GIF动画加载和播放流程,旨在说明流程和主要细节点。 ios原有加载缺陷分析 大家知道在 iOS 中处理过 GIF 图片, 如果通过原生系统提供的能力, 可能只有两种方式。 并且这两种方式都不是专门针对于 GIF 的解决方案,更像是一种 hack。 第一种方
xiangzhihong
2018/01/26
1.7K0
Android保存图片到相册(适配android 10以下及以上)
Android保存图片到相册 效果图 遇见平江路 代码实现 activity_main.xml MainActivity DownloadPhotoUtil ImageUtil 效果图 遇见平江路
是阿超
2022/10/05
4.6K0
Android保存图片到相册(适配android 10以下及以上)
Flutter动图加载机制解析
今天继续研究下 Flutter 是怎么处理动图的。Flutter 的 Image 加载默认会支持 gif、webp 等动态图片。在之前的文章中,我们会看到不同类型的图片加载逻辑是大致一样的,只是异步加载的逻辑不一样,
烧麦程
2022/05/10
1.6K0
Flutter动图加载机制解析
相关推荐
SVGA源码解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档