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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值