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:
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 AnimationLoop
auf. 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 pageIsActive
true
. 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:
Beachten Sie, dass das SKPaint
Objekt in einem using
Block erstellt wird. Wie viele SkiaSharp-Klassen SKPaint
abgeleitet SKObject
von , von denen SKNativeObject
abgeleitet 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: