YY-SVGA动画框架


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

<com.opensource.svgaplayer.SVGAImageView
   android:id="@+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采用了第二种方法,这样可以减少播放器的工作量,同时框架的通用性得到了提高,只要提供不同动画格式的转换工具都可以使用这个框架进行播放

动画层次结构

 

 

绘制过程

绘制位图

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)
   }
}
绘制矢量图形

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

 

 

 

 

 

 

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM