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
所以性能上面應該說還有優化的空間,解析的過程會造成卡頓