YY-SVGA动画框架

写在个人微信订阅号:https://mp.weixin.qq.com/s/mBxwTQjRdQaRqR_43aMhYg

YY最近开源了一个动画框架,可以直接把设计师做好的动画源文件转换成特定格式,并且在Android,iOS,Web三端进行播放,集成很方便,所以尝试了一下,效果很酷炫,记录一下心得体验。


  • demo地址:https://github.com/LittleNum/SVGA-Samples.git

  • 框架地址:https://github.com/yyued/SVGAPlayer-Android

动画源文件转换工具:

  • AE——https://github.com/yyued/SVGA-AEConverter

  • Flash——https://github.com/yyued/SVGA-FLConverter

框架的起源和过程可以看这两篇文章:

  • https://mp.weixin.qq.com/s/f_ldQtMpA7GdQWDP40V45w

  • https://mp.weixin.qq.com/s/CUUrJGLObtE6yX8NGDveGw

使用方法:

动画制作:

可以尝试使用AdobeAfter Effects CC 2017制作简单的动画

并利用提供的转换工具,得到.svga文件

播放:

添加依赖,播放.svga文件

add JitPack.io repo build.gradle

allprojects {
    repositories {
        …
        maven {
            url ‘https://jitpack.io‘
        }
    }
}

add dependency to build.gradle

compile ‘com.github.yyued:SVGAPlayer-Android:2.0.3

解决的问题:

  • 传统的帧动画,属性动画,gif或者webP无法满足使用需求 ;

  • 增强动画显示效果,减轻程序员的代码量,做到直接展示设计效果;

  • 提升性能,减小动画文件大小,动态更新动画。

使用场景:

直接使用SVGAImageView

"@+id/imageView"
    android:layout_width="0dp"
    android:layout_height="200dp"
    android:background="@color/colorPrimary"
    android:scaleType="fitCenter"
    app:antiAlias="true"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:source="angel.svga"/>

网络下载:

URL url = null;try {
    url = new URL(img);
} catch (MalformedURLException e) {
    e.printStackTrace();
}
parser.parse(url, new SVGAParser.ParseCompletion() {    @Override
    public void onComplete(SVGAVideoEntity mSVGAVideoEntity) {
        SVGADrawable drawable = new SVGADrawable(mSVGAVideoEntity);
        mSVGAImageView.setImageDrawable(drawable);
        mSVGAImageView.startAnimation();
    }    @Override
    public void onError() {
        Toast.makeText(PreviewActivity.this, "parse error!", Toast.LENGTH_SHORT).show();
    }
});

动态图像或文本

parser.parse(img, new SVGAParser.ParseCompletion() {    @Override
    public void onComplete(SVGAVideoEntity mSVGAVideoEntity) {
        SVGADynamicEntity dynamicItem = new SVGADynamicEntity();
        SVGADrawable drawable = new SVGADrawable(mSVGAVideoEntity, dynamicItem);
        TextPaint textPaint = new TextPaint();
        textPaint.setTextSize(30);
        textPaint.setFakeBoldText(true);
        textPaint.setARGB(0xff, 0x00, 0x00, 0x00);
        textPaint.setShadowLayer((float) 1.0, (float) 0.0, (float) 1.0, Color.BLACK); // 各种配置
        //解压.svga可以看到所有的图片,填入图片名称
        dynamicItem.setDynamicText("TEXT!", textPaint, "yu11");
        mSVGAImageView.setImageDrawable(drawable);
        mSVGAImageView.startAnimation();
    }    @Override
    public void onError() {
        Toast.makeText(PreviewActivity.this, "parse error!", Toast.LENGTH_SHORT).show();
    }
});

原理:矢量动画的原理

导出动画时间轴,包含位图,关键帧,矢量路径,样式

  • 根据关键帧进行动画还原,播放器负责插值计算和绘制

    • 播放器过于复杂,需要处理高阶插值计算,如二阶方程,贝塞尔曲线

  • 把所有的动画帧在导出时计算完成,播放器只负责绘制

    • 播放器实现简单,同时对不同的动画格式支持好,只要提供对应的转换器即可

SVGA采用了第二种方法,这样可以减少播放器的工作量,同时框架的通用性得到了提高,只要提供不同动画格式的转换工具都可以使用这个框架进行播放

动画层次结构


YY-SVGA动画框架_第1张图片

绘制过程

  1. 绘制位图

private fun drawImage(sprite: SVGADrawerSprite, scaleType: ImageView.ScaleType) {
    val canvas = this.canvas ?: return
    (dynamicItem.dynamicImage[sprite.imageKey] ?: videoItem.images[sprite.imageKey])?.let {
        sharedPaint.reset()
        sharedContentTransform.reset()
        sharedPaint.isAntiAlias = videoItem.antiAlias
        sharedPaint.alpha = (sprite.frameEntity.alpha * 255).toInt()
        performScaleType(scaleType)
        sharedContentTransform.preConcat(sprite.frameEntity.transform)
        sharedContentTransform.preScale((sprite.frameEntity.layout.width / it.width).toFloat(),
                (sprite.frameEntity.layout.width / it.width).toFloat())        if (sprite.frameEntity.maskPath != null) {
            val maskPath = sprite.frameEntity.maskPath ?: return@let
            canvas.save()
            sharedPath.reset()
            maskPath.buildPath(sharedPath)
            sharedPath.transform(sharedContentTransform)
            canvas.clipPath(sharedPath)
            canvas.drawBitmap(it, sharedContentTransform, sharedPaint)
            canvas.restore()
        }        else {
            canvas.drawBitmap(it, sharedContentTransform, sharedPaint)
        }
        drawText(it, sprite)
    }
}
  1. 绘制矢量图形

private fun drawShape(sprite: SVGADrawerSprite, scaleType: ImageView.ScaleType) {
    val canvas = this.canvas ?: return
    sharedContentTransform.reset()
    performScaleType(scaleType)
    sharedContentTransform.preConcat(sprite.frameEntity.transform)
    sprite.frameEntity.shapes.forEach { shape ->
        sharedPath.reset()
        shape.buildPath()
        shape.shapePath?.let {
            sharedPath.addPath(it)
        }        if (!sharedPath.isEmpty) {
            val thisTransform = Matrix()
            shape.transform?.let {
                thisTransform.postConcat(it)
            }
            thisTransform.postConcat(sharedContentTransform)
            sharedPath.transform(thisTransform)
            shape.styles?.fill?.let {                if (it != 0x00000000) {
                    sharedPaint.reset()
                    sharedPaint.color = it
                    sharedPaint.alpha = (sprite.frameEntity.alpha * 255).toInt()
                    sharedPaint.isAntiAlias = true
                    if (sprite.frameEntity.maskPath !== null) canvas.save()
                    sprite.frameEntity.maskPath?.let { maskPath ->
                        sharedPath2.reset()
                        maskPath.buildPath(sharedPath2)
                        sharedPath2.transform(this.sharedContentTransform)
                        canvas.clipPath(sharedPath2)
                    }
                    canvas.drawPath(sharedPath, sharedPaint)                    if (sprite.frameEntity.maskPath !== null) canvas.restore()
                }
            }
            shape.styles?.strokeWidth?.let {                if (it > 0) {
                    sharedPaint.reset()
                    sharedPaint.alpha = (sprite.frameEntity.alpha * 255).toInt()
                    resetShapeStrokePaint(shape)                    if (sprite.frameEntity.maskPath !== null) canvas.save()
                    sprite.frameEntity.maskPath?.let { maskPath ->
                        sharedPath2.reset()
                        maskPath.buildPath(sharedPath2)
                        sharedPath2.transform(this.sharedContentTransform)
                        canvas.clipPath(sharedPath2)
                    }
                    canvas.drawPath(sharedPath, sharedPaint)                    if (sprite.frameEntity.maskPath !== null) canvas.restore()
                }
            }
        }
    }
}

缺点

  • 对AE动画支持有限的效果和类型

  • TEXT不支持

  • AE插件不支持,粒子效果

  • 复杂动画转换较慢

  • 不适合交互的场景

性能

以全屏的angel.svga为例,通过Android Profiler查看cpu和内存信息

  • 动画文件大小: 299k

  • CPU:解析50%,播放5%左右

  • 内存:dump内存,总内存在9M左右,整个库的对象大小,实际包含其他对象,内存要大于9M


YY-SVGA动画框架_第2张图片


YY-SVGA动画框架_第3张图片



所以性能上面应该说还有优化的空间,解析的过程会造成卡顿

你可能感兴趣的:(Android,android,动画,SVGA)