在上一篇博文《 "[UWP]在UWP平臺中使用Lottie動畫" 》中我簡單介紹了一下LottieUWP項目以及如何使用它呈現Lottie動畫,這篇文章里我們來講點進階的東西——緩存Lottie動畫幀。 為什麼會有這樣的需求呢? 有兩方面原因: 直接在XAML中使用Lottie動畫時,是邊播放邊渲染 ...
在上一篇博文《[UWP]在UWP平臺中使用Lottie動畫》中我簡單介紹了一下LottieUWP項目以及如何使用它呈現Lottie動畫,這篇文章里我們來講點進階的東西——緩存Lottie動畫幀。
為什麼會有這樣的需求呢?
有兩方面原因:
- 直接在XAML中使用Lottie動畫時,是邊播放邊渲染,計算量比較大,某些Lottie文件會非常吃性能!另外也會存在渲染不正確(有黑色區域)的情況,但是如果我們把每一幀緩存下來,自己控制播放的話,性能會提升很多!
- 應用於視頻合成時(給視頻添加Lottie動畫掛件),需要獲取每一時刻的動畫幀圖像(UWP媒體編輯以及視頻合成的相關知識也很多,有時間我會整理一下,分享點乾貨)。
獲取Lottie動畫幀
在上一篇中我們使用了LottieAnimationView
控制項來播放Lottie動畫,其實另一個類LottieDrawable
也可以完成同樣的工作,並且更易擴展。
下麵我們就來修改下LottieDrawable
類,讓它可以返回給我們某一時刻的幀圖像。
在LottieDrawable
類中,Lottie動畫的播放進度由Progress
屬性控制,而實際上的呈現則是使用了Win2D中的CanvasAnimatedControl
控制項來承載繪製目標。
這樣的話,其實我們要做的就很簡單了。我們可以新增一個GetCurrentFrame
方法,使用CanvasRenderTarget
作為繪製目標,將CanvasAnimatedControl
的Draw事件中的繪製邏輯轉移過來即可。
具體代碼如下:
/// <summary>
/// 獲取當前進度時的Lottie圖像
/// </summary>
/// <param name="resourceCreator"></param>
/// <param name="scaleX">橫向縮放倍數</param>
/// <param name="scaleY">縱向縮放倍數</param>
/// <returns></returns>
public CanvasBitmap GetCurrentFrame(ICanvasResourceCreator resourceCreator, float scaleX, float scaleY)
{
lock (this)
{
var width = _composition.Bounds.Width * scaleX;
var height = _composition.Bounds.Height * scaleY;
var commandList = new CanvasRenderTarget(resourceCreator, (float)width, (float)height, 96f);
using (var session = commandList.CreateDrawingSession())
{
if (_bitmapCanvas == null || _bitmapCanvas.Width < width || _bitmapCanvas.Height < height)
{
_bitmapCanvas?.Dispose();
_bitmapCanvas = new BitmapCanvas(width, height);
}
using (_bitmapCanvas.CreateSession(resourceCreator.Device, (float)width,
(float)height, session))
{
_bitmapCanvas.Clear(Colors.Transparent);
LottieLog.BeginSection("Drawable.Draw");
if (_compositionLayer == null)
{
return null;
}
_matrix.Reset();
_matrix = MatrixExt.PreScale(_matrix, scaleX, scaleY);
_compositionLayer.Draw(_bitmapCanvas, _matrix, _alpha);
LottieLog.EndSection("Drawable.Draw");
}
}
return commandList;
}
}
有一點要註意的是這裡的繪製目標使用了CanvasRenderTarget
,切勿使用CanvasCommandList
,區別在於CanvasRenderTarget
有固定大小的尺寸,而CanvasCommandList
則沒有固定的尺寸(實際上時無限大的),使用CanvasCommandList
作為繪製目標將會引起某些Lottie動畫繪製時丟失部分內容,具體可參見我在LottieUWP項目上提的這個Issue 。
緩存Lottie動畫幀
有了上面添加的GetCurrentFrame
方法後,我們就可以通過修改Progress
來獲取Lottie動畫中每一時刻的幀圖像了。
我編寫了一個緩存Lottie動畫幀的方法:
protected List<CanvasBitmap> CacheLottieFrames;
/// <summary>
/// 緩存Lottie動畫幀
/// </summary>
/// <param name="width">緩存圖像的寬</param>
/// <param name="height">緩存圖像的高</param>
/// <param name="frameRate">緩存的幀率</param>
/// <returns></returns>
private async Task InitLottieFrame(double width, double height, int frameRate)
{
await Task.Run(() =>
{
lock (_lockObj)
{
if (lottieDrawable != null)
{
var duration = lottieDrawable.Composition.Duration;
var scaleX = width / lottieDrawable.Composition.Bounds.Width;
var scaleY = height / lottieDrawable.Composition.Bounds.Height;
var timeGap = 1d / frameRate;
CacheLottieFrames = new List<CanvasBitmap>();
var device = CanvasDevice.GetSharedDevice();
for (var i = 0d; i < duration; i += timeGap)
{
lottieDrawable.Progress = (float)(i / duration);
var renderTarget =
new CanvasRenderTarget(device, (float)CanvasWidth, (float)CanvasHeight, 96f);
using (var session = renderTarget.CreateDrawingSession())
{
session.Clear(Colors.Transparent);
var effectImg = lottieDrawable.GetCurrentFrame(device, (float)scaleX, (float)scaleY);
if (effectImg != null)
session.DrawImage(effectImg);
}
CacheLottieFrames.Add(renderTarget);
}
}
}
});
}
我們也可以在LottieDrawable.Composition
中獲取到幀的總數量以及幀率,以此為依據來 獲取幀,但是我在這個方法里沒有使用,因為我想依據傳入的幀率來控制獲取的幀數量,避免緩存多餘的幀占據記憶體空間。
結尾
有關於UWP使用Lottie動畫的相關博文到這裡就結束了,這段時間接觸下來,我的感受是Lottie動畫真的挺好玩效果也很棒。在LottieFiles網站大家可以找到各種有趣好玩的Lottie動畫,當然有能力的也可以自己製作。
本篇博客到此結束!謝謝大家閱讀!