SVGA源码解析

2022-12-13 15:02:02 浏览数 (1)

SVGA源码解析

数据解析-SVGAParser

主要是decodeFromInputStream方法,读取解析来自URL的svga文件,并缓存成本地文件

代码语言:text复制
fun decodeFromInputStream(
            inputStream: InputStream,
            cacheKey: String,
            callback: ParseCompletion?
    ) {
        threadPoolExecutor.execute {
            try {
                readAsBytes(inputStream)?.let { bytes ->
                    threadPoolExecutor.execute {
                        // 缓存成本地文件,这边就延伸一个问题,不会默认缓存到内存,每次都需要重新load
                        SVGACache.buildSvgaFile(cacheKey).let { cacheFile ->
                            cacheFile.takeIf { !it.exists() }?.createNewFile()
                            FileOutputStream(cacheFile).write(bytes)
                        }
                    }
                    //  protobuf 解析成SVGAVideoEntity
                    inflate(bytes)?.let { inflateBytes ->
                        val videoItem = SVGAVideoEntity(
                                MovieEntity.ADAPTER.decode(inflateBytes),
                                File(cacheKey),
                                mFrameWidth,
                                mFrameHeight
                        )
                    }
                }
            }
    }

动画实体类-SVGAVideoEntity

看一下SVGAVideoEntity的属性和构造

代码语言:text复制
    var antiAlias = true  //抗锯齿
    var movieItem: MovieEntity? = null // 解析方法传进来的MovieEntity

    var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0) // 播放大小
        private set

    var FPS = 15 // 设置fps,帧率
        private set

    var frames: Int = 0 //frame的个数
        private set

    internal var spriteList: List<SVGAVideoSpriteEntity> = emptyList() // 每一帧的信息集合
    internal var imageMap = HashMap<String, Bitmap>() // Bitmap缓存,每一帧绘制可以重复利用

    constructor(entity: MovieEntity, cacheDir: File, frameWidth: Int, frameHeight: Int) {
        this.mFrameWidth = frameWidth
        this.mFrameHeight = frameHeight
        this.mCacheDir = cacheDir
        this.movieItem = entity
        entity.params?.let(this::setupByMovie)
        try {
            // 解析图片并且缓存到map中
            parserImages(entity)
        } catch (e: Exception) {
            e.printStackTrace()
        } catch (e: OutOfMemoryError) {
            e.printStackTrace()
        }
        resetSprites(entity)
    }

    private fun parserImages(obj: MovieEntity) {
        obj.images?.entries?.forEach { entry ->
            val byteArray = entry.value.toByteArray()
            if (byteArray.count() < 4) {
                return@forEach
            }
            val fileTag = byteArray.slice(IntRange(0, 3))
            if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
                return@forEach
            }
            val filePath = generateBitmapFilePath(entry.value.utf8(), entry.key)
            createBitmap(byteArray, filePath)?.let { bitmap ->
                imageMap[entry.key] = bitmap
            }
        }
    }

现在拿到了实体类SVGAVideoEntity,接下里就用它去构造SVGADrawable

绘制实体-SVGADrawable

Drawable

官方介绍

A Drawable is a general abstraction for "something that can be drawn." Most often you will deal with Drawable as the type of resource retrieved for drawing things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms. Unlike a View, a Drawable does not have any facility to receive events or otherwise interact with the user.

简单翻译:

Drawable 是 “所有可绘制东西” 的一个抽象,大多数时候,我们只需要把各种不同类型的资源作为转化为 drawable,然后 View 会帮我们把它渲染到屏幕上。Drawable 类提供了一个通用 API,用于解析转化各种可视资源到 Canvas,跟 View 不一样,Drawable 不能接受任何事件以及用户交互。

SVGADrawable也是一个Drawable,内容比较简单。它的绘制工作交给SVGACanvasDrawer去处理。

代码语言:text复制
class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity): Drawable() {
    // 一般使用这个构造,只传入SVGAVideoEntity, SVGADynamicEntity是动态修改某些图片或者其他属性使用
    constructor(videoItem: SVGAVideoEntity): this(videoItem, SVGADynamicEntity())

    var cleared = true
        internal set (value) {
            if (field == value) {
                return
            }
            field = value
            invalidateSelf()
        }

    // 设置当前的frame,并且触发重绘
    var currentFrame = 0
        internal set (value) {
            if (field == value) {
                return
            }
            field = value
            invalidateSelf()
        }

    var scaleType: ImageView.ScaleType = ImageView.ScaleType.MATRIX

    private val drawer = SVGACanvasDrawer(videoItem, dynamicItem)

    override fun draw(canvas: Canvas?) {
        if (cleared) {
            return
        }
        // 绘制交给SVGACanvasDrawer处理
        canvas?.let {
            drawer.drawFrame(it,currentFrame, scaleType)
        }
    }

绘制者-SVGACanvasDrawer

核心方法:

代码语言:text复制
override fun drawFrame(canvas: Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
        super.drawFrame(canvas,frameIndex, scaleType)
        val sprites = requestFrameSprites(frameIndex) // 获取当前帧的Sprite列表
        sprites.forEachIndexed { index, svgaDrawerSprite ->
            svgaDrawerSprite.imageKey?.let {
                if (!hasMatteLayer || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    // Normal sprite
                    drawSprite(svgaDrawerSprite, canvas, frameIndex)
                    // Continue
                    return@forEachIndexed
                }
            }
    }

这边为啥获取的是一个列表?遍历绘制?不是说是按照一帧一帧绘制的吗?

主要是 SVGAVideoSpriteEntity: 他保存的是imageKey在每一帧下对应的属性(alpha,layout等等)

代码语言:text复制
class SVGAVideoSpriteEntity {

    val imageKey: String?

    val frames: List<SVGAVideoSpriteFrameEntity>
}

所以此处通过frameIndex获取每个图片(imageKey)对应的在这一帧的属性,位置,大小,透明度等。

代码语言:text复制
private fun drawSprite(sprite: SVGADrawerSprite, canvas :Canvas, frameIndex: Int) {PonyCui, 3 years ago: • add DynamicDrawer.
        drawImage(sprite, canvas) // 画图,移动path
        drawShape(sprite, canvas) // 边界
        drawDynamic(sprite, canvas, frameIndex) // 动态设置
    }

    private fun drawImage(sprite: SVGADrawerSprite, canvas :Canvas) {
        val imageKey = sprite.imageKey ?: return
        val isHidden = dynamicItem.dynamicHidden[imageKey] == true
        if (isHidden) { return }
        val bitmapKey = imageKey.replace(".matte", "")
        val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey]) ?: return // 获取缓存bitmap
        val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
        val paint = this.sharedValues.sharedPaint()
        paint.isAntiAlias = videoItem.antiAlias
        paint.isFilterBitmap = videoItem.antiAlias
        paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
        if (sprite.frameEntity.maskPath != null) {
            val maskPath = sprite.frameEntity.maskPath ?: return
            canvas.save()
            val path = this.sharedValues.sharedPath()
            maskPath.buildPath(path)
            path.transform(frameMatrix)
            canvas.clipPath(path)
            frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
            if (!drawingBitmap.isRecycled) {
                canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
            }
            canvas.restore()
        }
        
        drawTextOnBitmap(canvas, drawingBitmap, sprite, frameMatrix)
    }

至此Drawable的绘制已经完成,它是通过每一帧的属性去绘制。每次绘制一帧。

然后看下Drawable容器。

SVAG容器-SVAGImageView

SVAGImageView继承于普通的ImageView没有太大的改动,主要是提供了解析SVGA文件,并且播放动画的能力。

代码语言:text复制
open class SVGAImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : ImageView(context, attrs, defStyleAttr) {
      
    fun startAnimation() {
        startAnimation(null, false)
    }
      
    fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
        stopAnimation(false)
        play(range, reverse)
    }
      
    private fun play(range: SVGARange?, reverse: Boolean) {
        LogUtils.info(TAG, "================ start animation ================")
        val drawable = getSVGADrawable() ?: return
        setupDrawable() 
        mStartFrame = Math.max(0, range?.location ?: 0)
        val videoItem = drawable.videoItem
        mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0)   (range?.length ?: Int.MAX_VALUE) - 1))
        val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame) // 帧数范围
        animator.interpolator = LinearInterpolator()
        animator.duration = ((mEndFrame - mStartFrame   1) * (1000 / videoItem.FPS) / generateScale()).toLong() //动画时间和FPS有关
        animator.repeatCount = if (loops <= 0) 99999 else loops - 1
        animator.addUpdateListener(mAnimatorUpdateListener) //添加监听,最终调用onAnimatorUpdate方法
        animator.addListener(mAnimatorListener)
        if (reverse) {
            animator.reverse()
        } else {
            animator.start()
        }
        mAnimator = animator
    }
      
   private fun onAnimatorUpdate(animator: ValueAnimator?) {
        val drawable = getSVGADrawable() ?: return
        drawable.currentFrame = animator?.animatedValue as Int // 更新SVGADrawable的currentFrame
        val percentage = (drawable.currentFrame   1).toDouble() / drawable.videoItem.frames.toDouble()
        callback?.onStep(drawable.currentFrame, percentage)
    }

可以看到SVAGImageView的播放动画其实就是更新SVGADrawable的currentFrame,看源码,触发invalidateSelf,其实就是Drawable重绘,走它的draw方法。这就回到了上面SVGADrawable的绘制原理。

代码语言:text复制
    var currentFrame = 0
        internal set (value) {
            if (field == value) {
                return
            }
            field = value
            invalidateSelf()
        }

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2z0jgktwcg84k

0 人点赞