SkiaSharp 中的基本動畫
探索如何以動畫顯示您的 SkiaSharp 圖形
您可以讓 中的 Xamarin.Forms SkiaSharp圖形產生動畫效果,方法是 PaintSurface
定期呼叫 方法,每次以稍微不同的方式繪製圖形。 以下是本文稍後所顯示的動畫,其中包含看似從中心展開的同心圓:
範例程式中的 [Pulsating Ellipse] 頁面會以動畫顯示橢圓形的兩個軸,使其看起來正在脈動,您甚至可以控制此脈動的速率。 PulsatingEllipsePage.xaml 檔案會具現化 和Slider
Label
Xamarin.Forms,以顯示滑桿的目前值。 這是整合與其他Xamarin.Forms檢視的常見方式SKCanvasView
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.PulsatingEllipsePage"
Title="Pulsating Ellipse">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Slider x:Name="slider"
Grid.Row="0"
Maximum="10"
Minimum="0.1"
Value="5"
Margin="20, 0" />
<Label Grid.Row="1"
Text="{Binding Source={x:Reference slider},
Path=Value,
StringFormat='Cycle time = {0:F1} seconds'}"
HorizontalTextAlignment="Center" />
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="2"
PaintSurface="OnCanvasViewPaintSurface" />
</Grid>
</ContentPage>
程序代碼後置檔案會具現化 Stopwatch
物件,做為高精確度時鐘。 覆寫會將 OnAppearing
pageIsActive
欄位設定為 true
,並呼叫名為 AnimationLoop
的方法。 覆寫會將 OnDisappearing
該 pageIsActive
欄位設定為 false
:
Stopwatch stopwatch = new Stopwatch();
bool pageIsActive;
float scale; // ranges from 0 to 1 to 0
public PulsatingEllipsePage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
pageIsActive = true;
AnimationLoop();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
pageIsActive = false;
}
方法AnimationLoop
會在 為 true
時pageIsActive
啟動 Stopwatch
,然後迴圈。 這基本上是一個「無限迴圈」,而頁面處於使用中狀態,但不會讓程式停止響應,因為迴圈會以 await
運算符呼叫 Task.Delay
結束,這可讓程式函式的其他部分。 要 Task.Delay
讓它在 1/30 秒之後完成的自變數。 這會定義動畫的幀速率。
async Task AnimationLoop()
{
stopwatch.Start();
while (pageIsActive)
{
double cycleTime = slider.Value;
double t = stopwatch.Elapsed.TotalSeconds % cycleTime / cycleTime;
scale = (1 + (float)Math.Sin(2 * Math.PI * t)) / 2;
canvasView.InvalidateSurface();
await Task.Delay(TimeSpan.FromSeconds(1.0 / 30));
}
stopwatch.Stop();
}
迴圈 while
會從取得循環時間 Slider
開始。 這是以秒為單位的時間,例如5。 第二個語句會計算時間的值t
。 若為 cycleTime
5, t
則每 5 秒從 0 增加到 1。 第二個語句中函 Math.Sin
式的自變數會每隔 5 秒從 0 到 2π 不等。 函 Math.Sin
式會傳回值,範圍從 0 到 1 回到 0,然後每隔 5 秒傳回至 –1 和 0,但值在值接近 1 或 –1 時會更慢變更。 值 1 會加入,因此值一律為正數,然後除以 2,因此值範圍從 1/2 到 1/2 到 0 到 1/2,但值大約在 1 和 0 左右時變慢。 這會儲存在 scale
欄位中,且 SKCanvasView
無效。
PaintSurface
方法會使用此值scale
來計算橢圓形的兩個軸:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float maxRadius = 0.75f * Math.Min(info.Width, info.Height) / 2;
float minRadius = 0.25f * maxRadius;
float xRadius = minRadius * scale + maxRadius * (1 - scale);
float yRadius = maxRadius * scale + minRadius * (1 - scale);
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = 50;
canvas.DrawOval(info.Width / 2, info.Height / 2, xRadius, yRadius, paint);
paint.Style = SKPaintStyle.Fill;
paint.Color = SKColors.SkyBlue;
canvas.DrawOval(info.Width / 2, info.Height / 2, xRadius, yRadius, paint);
}
}
方法會根據顯示區域的大小計算最大半徑,並根據最大半徑來計算最小半徑。 scale
值會在 0 和 1 到 0 之間產生動畫效果,因此方法會使用該值來計算 xRadius
和 yRadius
之間的minRadius
maxRadius
範圍。 這些值可用來繪製和填滿橢圓形:
請注意, SKPaint
物件是在 區塊中 using
建立的。 如同許多 SkiaSharp 類別 SKPaint
衍生自 SKObject
,其衍生自 SKNativeObject
,其實作 IDisposable
介面。 SKPaint
會 Dispose
覆寫 方法來釋放 Unmanaged 資源。
放入SKPaint
using
區塊可確保Dispose
在區塊結尾呼叫 ,以釋放這些非受控資源。 無論如何,當 .NET 垃圾收集行程釋放物件所使用的 SKPaint
記憶體時,就會發生這種情況,但在動畫程序代碼中,最好以更有序的方式主動釋放記憶體。
在此特定案例中,更好的解決方案是建立兩 SKPaint
個物件一次,並將它們儲存為字段。
這就是 展開圓形 動畫的功能。 類別 ExpandingCirclesPage
的開頭是定義數個 SKPaint
欄位,包括物件:
public class ExpandingCirclesPage : ContentPage
{
const double cycleTime = 1000; // in milliseconds
SKCanvasView canvasView;
Stopwatch stopwatch = new Stopwatch();
bool pageIsActive;
float t;
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke
};
public ExpandingCirclesPage()
{
Title = "Expanding Circles";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
...
}
此程式會使用不同的方法來根據 Xamarin.FormsDevice.StartTimer
方法進行動畫。 欄位 t
會以每毫秒 0 到 1 的 cycleTime
動畫顯示:
public class ExpandingCirclesPage : ContentPage
{
...
protected override void OnAppearing()
{
base.OnAppearing();
pageIsActive = true;
stopwatch.Start();
Device.StartTimer(TimeSpan.FromMilliseconds(33), () =>
{
t = (float)(stopwatch.Elapsed.TotalMilliseconds % cycleTime / cycleTime);
canvasView.InvalidateSurface();
if (!pageIsActive)
{
stopwatch.Stop();
}
return pageIsActive;
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
pageIsActive = false;
}
...
}
處理程式 PaintSurface
會繪製五個具有動畫弧度的同心圓。 baseRadius
如果變數計算為 100,則t
動畫從 0 到 1,則五個圓形的弧度會從 0 增加到 100、100 到 200、200 到 300、300 到 400 和 400 到 500。 對於大部分的圓形而言, strokeWidth
是50,但第一個圓形則 strokeWidth
以0到50的動畫顯示。 對於大部分圓圈而言,色彩為藍色,但對於最後一個圓形而言,色彩會從藍色動畫呈現為透明。 請注意建構函式的第 SKColor
四個自變數,指定不透明度:
public class ExpandingCirclesPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);
float baseRadius = Math.Min(info.Width, info.Height) / 12;
for (int circle = 0; circle < 5; circle++)
{
float radius = baseRadius * (circle + t);
paint.StrokeWidth = baseRadius / 2 * (circle == 0 ? t : 1);
paint.Color = new SKColor(0, 0, 255,
(byte)(255 * (circle == 4 ? (1 - t) : 1)));
canvas.DrawCircle(center.X, center.Y, radius, paint);
}
}
}
結果是當等於 0 時t
,影像看起來會和等於 1 時t
相同,而圓形似乎會永遠繼續展開: