写在个人微信订阅号: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无法满足使用需求 ;
增强动画显示效果,减轻程序员的代码量,做到直接展示设计效果;
提升性能,减小动画文件大小,动态更新动画。
"@+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
所以性能上面应该说还有优化的空间,解析的过程会造成卡顿