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