Animacja podstawowa w skiaSharp
Dowiedz się, jak animować grafikę SkiaSharp
Możesz animować grafikę SkiaSharp w programie Xamarin.Forms , powodując PaintSurface
, że metoda jest wywoływana okresowo, za każdym razem rysując grafikę nieco inaczej. Poniżej przedstawiono animację pokazaną w dalszej części tego artykułu z koncentrycznymi okręgami, które pozornie rozszerzają się od środka:
Strona Pulsująca elipsa w programie przykładowym animuje dwie osie wielokropka, aby wydawała się pulsować, a nawet kontrolować szybkość tego pulsowania. Plik PulsatingEllipsePage.xaml tworzy wystąpienie elementu Xamarin.FormsSlider
i , Label
aby wyświetlić bieżącą wartość suwaka. Jest to typowy sposób integrowania elementu SKCanvasView
z innymi Xamarin.Forms widokami:
<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>
Plik będący za kodem tworzy wystąpienie Stopwatch
obiektu, który będzie służył jako zegar o wysokiej precyzji. Przesłonięcia OnAppearing
ustawia pageIsActive
pole na true
i wywołuje metodę o nazwie AnimationLoop
. Przesłonięcia OnDisappearing
ustawia to pageIsActive
pole na 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;
}
Metoda AnimationLoop
uruchamia Stopwatch
pętle , a następnie wykonuje pętle , gdy pageIsActive
ma wartość true
. Jest to zasadniczo "nieskończona pętla", gdy strona jest aktywna, ale nie powoduje zawieszenia programu, ponieważ pętla kończy się wywołaniem Task.Delay
z operatorem await
, co pozwala innym częściom funkcji programu. Argument Task.Delay
powoduje jego ukończenie po 1/30 sekundy. Definiuje szybkość klatek animacji.
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();
}
Pętla while
rozpoczyna się od uzyskania czasu cyklu z .Slider
Jest to czas w sekundach, na przykład 5. Druga instrukcja oblicza wartość t
czasu. W przypadku wartości cycleTime
5 t
zwiększa się z zakresu od 0 do 1 co 5 sekund. Argument Math.Sin
funkcji w drugiej instrukcji waha się od 0 do 2π co 5 sekund. Funkcja Math.Sin
zwraca wartość z zakresu od 0 do 1 z powrotem do 0, a następnie do –1 i 0 co 5 sekund, ale z wartościami, które zmieniają się wolniej, gdy wartość jest zbliżona do 1 lub –1. Wartość 1 jest dodawana, więc wartości są zawsze dodatnie, a następnie są dzielone przez 2, więc wartości wahają się od 1/2 do 1/2 do 0 do 1/2, ale wolniej, gdy wartość wynosi około 1 i 0. Jest on przechowywany w scale
polu i SKCanvasView
jest unieważniany.
Metoda PaintSurface
używa tej scale
wartości do obliczenia dwóch osi wielokropka:
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);
}
}
Metoda oblicza maksymalny promień na podstawie rozmiaru obszaru wyświetlania i minimalnego promienia na podstawie maksymalnego promienia. Wartość scale
jest animowana z zakresu od 0 do 1 do 0, więc metoda używa tej metody do obliczenia wartości xRadius
i yRadius
, które wahają się między minRadius
i maxRadius
. Te wartości są używane do rysowania i wypełniania wielokropka:
Zwróć uwagę, że SKPaint
obiekt jest tworzony w using
bloku. Podobnie jak wiele klas SKPaint
SkiaSharp pochodzi z SKObject
klasy , która pochodzi z SKNativeObject
klasy , która implementuje IDisposable
interfejs. SKPaint
zastępuje metodę Dispose
zwalniania niezarządzanych zasobów.
Umieszczenie SKPaint
bloku using
zapewnia wywołanie Dispose
na końcu bloku w celu zwolnienia tych niezarządzanych zasobów. Dzieje się tak, gdy pamięć używana przez SKPaint
obiekt jest zwalniana przez moduł odśmiecania pamięci platformy .NET, ale w kodzie animacji najlepiej proaktywnie zwalniać pamięć w bardziej uporządkowany sposób.
Lepszym rozwiązaniem w tym konkretnym przypadku byłoby utworzenie dwóch SKPaint
obiektów raz i zapisanie ich jako pól.
To właśnie robi animacja Expanding Circles . Klasa ExpandingCirclesPage
rozpoczyna się od zdefiniowania kilku pól, w tym SKPaint
obiektu:
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;
}
...
}
Ten program używa innego podejścia do animacji na Xamarin.FormsDevice.StartTimer
podstawie metody . Pole t
jest animowane od 0 do 1 co cycleTime
milisekundy:
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;
}
...
}
Program PaintSurface
obsługi rysuje pięć koncentrycznych okręgów z animowanym promieniem. Jeśli zmienna baseRadius
jest obliczana jako 100, to jak t
jest animowana z zakresu od 0 do 1, promienie pięciu okręgów zwiększają się z 0 do 100, 100 do 200, 200 do 300, 300 do 400 i 400 do 500. Dla większości okręgów jest strokeWidth
50, ale dla pierwszego koła, strokeWidth
animuje się z zakresu od 0 do 50. W przypadku większości okręgów kolor jest niebieski, ale dla ostatniego okręgu kolor jest animowany z niebieskiego na przezroczysty. Zwróć uwagę na czwarty argument SKColor
konstruktora, który określa nieprzezroczystość:
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);
}
}
}
Wynik polega na tym, że obraz wygląda tak samo, gdy t
równa się 0, gdy t
równa 1, a okręgi wydają się nadal rozszerzać na zawsze: