Share via


Grundlegende Animation in SkiaSharp

Erfahren Sie, wie Sie Ihre SkiaSharp-Grafiken animieren

Sie können SkiaSharp-Grafiken Xamarin.Forms animieren, indem Sie die PaintSurface Methode regelmäßig aufrufen, jedes Mal, wenn Sie die Grafiken etwas anders zeichnen. Nachfolgend finden Sie eine Animation, die weiter unten in diesem Artikel mit konzentrischen Kreisen gezeigt wird, die scheinbar aus der Mitte erweitert werden:

Mehrere konzentrische Kreise, die scheinbar aus der Mitte erweitert werden

Die Pulsierende Ellipse-Seite im Beispielprogramm animiert die beiden Achsen einer Ellipse so, dass es pulsierend erscheint, und Sie können sogar die Rate dieser Pulsation steuern. Die Datei PulsatingEllipsePage.xaml instanziiert ein Xamarin.FormsSlider und einLabel, um den aktuellen Wert des Schiebereglers anzuzeigen. Dies ist eine gängige Möglichkeit, eine SKCanvasView in andere Xamarin.Forms Ansichten zu integrieren:

<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>

Die CodeBehind-Datei instanziiert ein Stopwatch Objekt, das als präzise Uhr dient. Die OnAppearing Außerkraftsetzung legt das pageIsActive Feld auf true und ruft eine Methode mit dem Namen AnimationLoopauf. Die OnDisappearing Außerkraftsetzung legt dieses pageIsActive Feld auf :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;
}

Die AnimationLoop Methode startet und Stopwatch wird dann in einer Schleife ausgeführt pageIsActivetrue. Dies ist im Wesentlichen eine "Unendliche Schleife", während die Seite aktiv ist, aber es führt nicht dazu, dass das Programm hängen bleibt, da die Schleife mit einem Aufruf an Task.Delay den await Operator endet, der andere Teile der Programmfunktion zulässt. Das Argument, das bewirkt, Task.Delay dass es nach 1/30 Sekunde abgeschlossen wird. Dadurch wird die Framerate der Animation definiert.

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();
}

Die while Schleife beginnt mit dem Abrufen einer Zykluszeit von der Slider. Dies ist eine Zeit in Sekunden, z. B. 5. Die zweite Anweisung berechnet einen Zeitwert.t Für einen cycleTime von 5 t erhöht sich von 0 auf 1 alle 5 Sekunden. Das Argument für die Funktion in der Math.Sin zweiten Anweisung reicht von 0 bis 2π alle 5 Sekunden. Die Math.Sin Funktion gibt einen Wert zwischen 0 und 1 zurück bis 0 zurück und dann alle 5 Sekunden auf –1 und 0 zurück, aber mit Werten, die sich langsamer ändern, wenn der Wert nahe 1 oder –1 liegt. Der Wert 1 wird addiert, sodass die Werte immer positiv sind und dann durch 2 dividiert werden, sodass die Werte zwischen 1/2 und 1/2 bis 0 bis 1/2 liegen, aber langsamer, wenn der Wert um 1 und 0 liegt. Dies wird im scale Feld gespeichert, und die SKCanvasView Datei ist ungültig.

Die PaintSurface Methode verwendet diesen scale Wert, um die beiden Achsen der Ellipse zu berechnen:

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);
    }
}

Die Methode berechnet einen maximalen Radius basierend auf der Größe des Anzeigebereichs und einen Minimalradius basierend auf dem maximalen Radius. Der scale Wert wird zwischen 0 und 1 und zurück zu 0 animiert, sodass die Methode diese zum Berechnen eines xRadius und yRadius dieser Bereiche zwischen minRadius und .maxRadius Diese Werte werden verwendet, um eine Ellipse zu zeichnen und auszufüllen:

Dreifacher Screenshot der Pulsating Ellipse-Seite

Beachten Sie, dass das SKPaint Objekt in einem using Block erstellt wird. Wie viele SkiaSharp-Klassen SKPaint abgeleitet SKObjectvon , von denen SKNativeObjectabgeleitet wird , von denen die IDisposable Schnittstelle implementiert wird. SKPaint setzt die Dispose Methode außer Kraft, um nicht verwaltete Ressourcen freizugeben.

Durch das Einfügen SKPaint eines using Blocks wird sichergestellt, dass Dispose am Ende des Blocks aufgerufen wird, um diese nicht verwalteten Ressourcen freizugeben. Dies geschieht trotzdem, wenn der vom SKPaint .NET Garbage Collector verwendete Speicher vom .NET Garbage Collector freigegeben wird, aber im Animationscode empfiehlt es sich, proaktiv Arbeitsspeicher auf geordnete Weise freizugeben.

Eine bessere Lösung in diesem Fall wäre es, zwei SKPaint Objekte einmal zu erstellen und als Felder zu speichern.

Dies ist die Funktion der Animation "Erweiternde Kreise ". Die ExpandingCirclesPage Klasse beginnt mit der Definition mehrerer Felder, einschließlich eines SKPaint Objekts:

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;
    }
    ...
}

Dieses Programm verwendet einen anderen Ansatz für Animationen basierend auf der Xamarin.FormsDevice.StartTimer Methode. Das t Feld wird von 0 bis 1 pro cycleTime Millisekunden animiert:

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;
    }
    ...
}

Der PaintSurface Handler zeichnet fünf konzentrische Kreise mit animiertem Bogen. Wenn die baseRadius Variable als 100 berechnet wird, t erhöhen sich die Radien der fünf Kreise von 0 bis 100, 100 bis 200, 200 bis 300, 300 bis 400 und 400 auf 500. Für die meisten Kreise ist dies strokeWidth 50, aber für den ersten Kreis werden die strokeWidth Animationen von 0 bis 50 animiert. Bei den meisten Kreisen ist die Farbe blau, aber für den letzten Kreis wird die Farbe von Blau bis transparent animiert. Beachten Sie das vierte Argument für den SKColor Konstruktor, der die Deckkraft angibt:

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);
        }
    }
}

Das Ergebnis ist, dass das Bild gleich 0 t aussieht, wenn t gleich 1 ist, und die Kreise scheinen für immer zu expandieren:

Dreifacher Screenshot der Seite