Animazione di base in SkiaSharp
Scopri come animare la grafica SkiaSharp
Puoi animare grafica SkiaSharp in Xamarin.Forms facendo in modo che il PaintSurface
metodo venga chiamato periodicamente, ogni volta che disegna la grafica in modo leggermente diverso. Ecco un'animazione illustrata più avanti in questo articolo con cerchi concentrici che sembrano espandersi dal centro:
La pagina Pulsating Ellipse nel programma di esempio anima i due assi di un'ellisse in modo che appaia pulsare, e si può anche controllare la frequenza di questa pulsazione. Il file PulsatingEllipsePage.xaml crea un'istanza Xamarin.FormsSlider
di e un oggetto Label
per visualizzare il valore corrente del dispositivo di scorrimento. Questo è un modo comune per integrare un oggetto SKCanvasView
con altre Xamarin.Forms visualizzazioni:
<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>
Il file code-behind crea un'istanza di un Stopwatch
oggetto da usare come orologio ad alta precisione. L'override OnAppearing
imposta il pageIsActive
campo su true
e chiama un metodo denominato AnimationLoop
. L'override OnDisappearing
imposta tale pageIsActive
campo su 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;
}
Il AnimationLoop
metodo avvia Stopwatch
e quindi esegue il ciclo mentre pageIsActive
è true
. Si tratta essenzialmente di un "ciclo infinito" mentre la pagina è attiva, ma non causa il blocco del programma perché il ciclo termina con una chiamata a Task.Delay
con l'operatore await
, che consente ad altre parti della funzione di programma. L'argomento per Task.Delay
fare in modo che venga completato dopo 1/30° secondo. In questo modo viene definita la frequenza dei fotogrammi dell'animazione.
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();
}
Il while
ciclo inizia ottenendo un tempo di ciclo dall'oggetto Slider
. Questo è un tempo in secondi, ad esempio 5. La seconda istruzione calcola un valore di t
per il tempo. Per un cycleTime
valore pari a 5, t
aumenta da 0 a 1 ogni 5 secondi. L'argomento della Math.Sin
funzione nella seconda istruzione varia da 0 a 2π ogni 5 secondi. La Math.Sin
funzione restituisce un valore compreso tra 0 e 1 a 0 e quindi a –1 e 0 ogni 5 secondi, ma con valori che cambiano più lentamente quando il valore è vicino a 1 o -1. Il valore 1 viene aggiunto in modo che i valori siano sempre positivi e quindi divisi per 2, quindi i valori vanno da 1/2 a 1/2 a 1/2 a 0/2, ma più lenti quando il valore è intorno a 1 e 0. Viene archiviato nel scale
campo e l'oggetto SKCanvasView
viene invalidato.
Il PaintSurface
metodo usa questo scale
valore per calcolare i due assi dell'ellisse:
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);
}
}
Il metodo calcola un raggio massimo in base alle dimensioni dell'area di visualizzazione e un raggio minimo in base al raggio massimo. Il scale
valore viene animato tra 0 e 1 e torna a 0, quindi il metodo usa tale valore per calcolare un oggetto xRadius
e yRadius
che è compreso tra minRadius
e maxRadius
. Questi valori vengono usati per disegnare e riempire un'ellisse:
Si noti che l'oggetto SKPaint
viene creato in un using
blocco. Come molte classi SKPaint
SkiaSharp derivano da SKObject
, che deriva da SKNativeObject
, che implementa l'interfaccia IDisposable
. SKPaint
esegue l'override del Dispose
metodo per rilasciare risorse non gestite.
L'inserimento SKPaint
di un using
blocco garantisce che Dispose
venga chiamato alla fine del blocco per liberare queste risorse non gestite. Ciò accade comunque quando la memoria usata dall'oggetto SKPaint
viene liberata dal Garbage Collector .NET, ma nel codice di animazione è preferibile essere proattiva nella liberare memoria in modo più ordinato.
Una soluzione migliore in questo caso specifico consiste nel creare due SKPaint
oggetti una sola volta e salvarli come campi.
Questo è il funzionamento dell'animazione Cerchi espansi . La ExpandingCirclesPage
classe inizia definendo diversi campi, tra cui un SKPaint
oggetto :
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;
}
...
}
Questo programma usa un approccio diverso all'animazione in base al Xamarin.FormsDevice.StartTimer
metodo . Il t
campo viene animato da 0 a 1 ogni cycleTime
millisecondo:
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;
}
...
}
Il PaintSurface
gestore disegna cinque cerchi concentrici con raggi animati. Se la baseRadius
variabile viene calcolata come 100, così come t
viene animata da 0 a 1, i raggi dei cinque cerchi aumentano da 0 a 100, 100 a 200, 200 a 300, 300 a 400 e 400 a 500. Per la maggior parte dei cerchi è strokeWidth
50, ma per il primo cerchio, l'animazione strokeWidth
va da 0 a 50. Per la maggior parte dei cerchi, il colore è blu, ma per l'ultimo cerchio il colore viene animato dal blu al trasparente. Si noti il quarto argomento del SKColor
costruttore che specifica l'opacità:
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);
}
}
}
Il risultato è che l'immagine ha lo stesso aspetto quando t
è uguale a 0 come quando t
è uguale a 1 e i cerchi sembrano continuare ad espandersi per sempre: