共用方式為


SkiaSharp 中的基本動畫

探索如何以動畫顯示您的 SkiaSharp 圖形

您可以讓 中的 Xamarin.Forms SkiaSharp圖形產生動畫效果,方法是 PaintSurface 定期呼叫 方法,每次以稍微不同的方式繪製圖形。 以下是本文稍後所顯示的動畫,其中包含看似從中心展開的同心圓:

幾個同心圓似乎從中心擴張

範例程式中的 [Pulsating Ellipse] 頁面會以動畫顯示橢圓形的兩個軸,使其看起來正在脈動,您甚至可以控制此脈動的速率。 PulsatingEllipsePage.xaml 檔案會具現化 和SliderLabelXamarin.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 物件,做為高精確度時鐘。 覆寫會將 OnAppearingpageIsActive 欄位設定為 true ,並呼叫名為 AnimationLoop的方法。 覆寫會將 OnDisappearingpageIsActive 欄位設定為 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會在 為 truepageIsActive啟動 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 之間產生動畫效果,因此方法會使用該值來計算 xRadiusyRadius 之間的minRadiusmaxRadius範圍。 這些值可用來繪製和填滿橢圓形:

Pulsating Ellipse 頁面的三個螢幕快照

請注意, SKPaint 物件是在 區塊中 using 建立的。 如同許多 SkiaSharp 類別 SKPaint 衍生自 SKObject,其衍生自 SKNativeObject,其實作 IDisposable 介面。 SKPaintDispose 覆寫 方法來釋放 Unmanaged 資源。

放入SKPaintusing區塊可確保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相同,而圓形似乎會永遠繼續展開:

展開圓形頁面的三個螢幕快照