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