Android应用内截动画生成Gif

Posted zhu_free

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android应用内截动画生成Gif相关的知识,希望对你有一定的参考价值。

最近在练习写动画和自定义View,打算可以在应用里写一个功能一键生成动画播放的gif,就像bilibili播放视频时的长按录gif那样,省去用androidStudio录屏然后mp4转gif的麻烦了。在网上找了一圈大部分用的都是一个叫AnimatedGifEncoder的东西,下载下来发现就是一个朴素的java文件…找了一下示例代码就开始用了。

先介绍一下基础的使用方法,语言用的是kotlin,java也差不到哪里去:

原理

关键方法是addFrame(Bitmap bitmap)把一帧一帧的bitmap添加好,设置一些参数,最后把字节输出到文件生成gif。

使用

基础参数

val animatedGifEncoder = AnimatedGifEncoder()
// 定义一个输出流
var byteArrayOutputStream = ByteArrayOutputStream()
animatedGifEncoder.start(byteArrayOutputStream)//start
animatedGifEncoder.setRepeat(0)//设置生成gif的开始播放次数,默认为1,0为立即开始播放
animatedGifEncoder.setDelay(20) // 每帧之间的间隔,毫秒

其他的一些方法可以看看源码

获取bitmap

这里bitmap可以选取相册里的图片,如果需要从App视图中截图,要使用canvas:

var tempBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.RGB_565)
var gifCanvas = Canvas(tempBitmap)
gifCanvas?.save()
// 如果有尺寸需要,对canvas绘图范围进行平移和剪切,尺寸需要仔细计算
val clipTop = (screenHeight - boxHeight) + boxCenterY - width.div(2f)
val clipBottom = (screenHeight - boxHeight) + boxCenterY + width.div(2f)
gifCanvas?.translate(0f, -clipTop)
gifCanvas?.clipRect(0f, -clipTop, width.toFloat(), clipBottom)
// 用canvas绘图,即绘制到bitmap里
rootView.draw(gifCanvas)
gifCanvas?.restore()
// 添加当前bitmap作为一帧
animatedGifEncoder.addFrame(tempBitmap)

处理文件

animatedGifEncoder.finish()// 结束添加
// 创建文件夹
val file = File(Environment.getExternalStorageDirectory().path + "/DuangJike")
if (!file.exists()) file.mkdir()
// 创建文件路径
val path = Environment.getExternalStorageDirectory().path + "/DuangJike/" + filename + ".gif"
// 创建输出流
val fos = FileOutputStream(path)
// 把字节流输出到文件中
byteArrayOutputStream.writeTo(fos)
byteArrayOutputStream.flush()
fos.flush()
byteArrayOutputStream.close()
fos.close()

在实际使用时,和动画播放的速度比起来添加一帧的操作(addFrame)堪比耗时操作,想要一秒多少帧基本上是痴心妄想,回头考虑把bitmap输出成文件再从文件导出来压缩一遍看看减小图片质量看看速度能不能快一些。
再与动画相结合时,我的设想是选择保存时将动画播放一遍,然后跟着多少毫秒截屏一次,但是因为太耗时,也没有写成一步操作,添加一帧完了基本上动画都放完了,最后采取的解决方案是在生成gif的情况下吧动画播放时间拉长,直到一秒一个关键帧,运行完以后把图片叠加成gif,播放起来还是很快的,但整体来说很耗时,不算太好。
一些关键代码:

// 自定义view的父类中
// 定义变量
var animLastTime = 0  // 动画持续时常,秒
val animatedGifEncoder = AnimatedGifEncoder()
var tempBitmap: Bitmap? = null
var gifCanvas: Canvas? = null
var byteArrayOutputStream = ByteArrayOutputStream()
var gifFlag = false
var gifFileName = ""
// 初始化
init 
    screenWidth = context.resources.displayMetrics.widthPixels
    screenHeight = context.resources.displayMetrics.heightPixels
    tempBitmap = Bitmap.createBitmap(screenWidth, screenWidth, Bitmap.Config.RGB_565)
    gifCanvas = Canvas(tempBitmap)


// 生成gif的函数
@Throws(IOException::class)
fun createGif(filename: String, width: Int) 
//        设置一个flag,播放动画时把时间拉长好有空处理bitmap
    gifFlag = true

    animatedGifEncoder.start(byteArrayOutputStream)//start
    animatedGifEncoder.setRepeat(0)//设置生成gif的开始播放次数,默认为10为立即开始播放
    animatedGifEncoder.setDelay(20) // 每帧之间的间隔,毫秒
    gifFileName = filename
    startAnimation()

    var i = 0

    val runnable = object : Runnable 
        override fun run() 
            if (i < animLastTime) 
                gifCanvas?.save()
                // 这里我打算截一个以主界面中心点为中心的正方形,所以宽高都是屏幕宽度
                val clipTop = (screenHeight - boxHeight) + boxCenterY - width.div(2f)
                val clipBottom = (screenHeight - boxHeight) + boxCenterY + width.div(2f)
                gifCanvas?.translate(0f, -clipTop)
                gifCanvas?.clipRect(0f, -clipTop, width.toFloat(), clipBottom)
                rootView.draw(gifCanvas)
                gifCanvas?.restore()
                i++
                Log.i(mTAG, "i = " + i)
                animatedGifEncoder.addFrame(tempBitmap)
                handler.postDelayed(this, 1000) // 设置足够长的间隔处理图片
             else 
                handler.removeCallbacks(this)
                Log.i(mTAG, "finish")
                animatedGifEncoder.finish()//finish
                val file = File(Environment.getExternalStorageDirectory().path + "/DuangJike")
                if (!file.exists()) file.mkdir()
                val path = Environment.getExternalStorageDirectory().path + "/DuangJike/" + filename + ".gif"
                val fos = FileOutputStream(path)
                byteArrayOutputStream.writeTo(fos)
                byteArrayOutputStream.flush()
                fos.flush()
                byteArrayOutputStream.close()
                fos.close()
            
        
    
    runnable.run()


// 子类中
override fun startAnimation() 
    rotateDegree = 0
    if (gifFlag) 
        animator?.duration = 18000
        animLastTime = 18
     else 
        animator?.duration = 3000
    
    super.startAnimation()

效果图:

源码:https://github.com/zhufree/DuangJike

以上是关于Android应用内截动画生成Gif的主要内容,如果未能解决你的问题,请参考以下文章

R语言ggplot2可视化:使用gganimate包和gapminder包为生成的动画文件gif设置尺寸分辨率

使用算法制作动画 GIF 的最佳方法是啥?

制作Android Demo GIF:程序演示效果GIF图录制

android 怎样显示gif动画

记录/捕获 Android 应用程序行为 - 转换为动画 GIF

如何在 Android 中从 JPEG 创建动画 GIF(开发)