Animation de base dans SkiaSharp
Découvrez comment animer vos graphiques SkiaSharp
Vous pouvez animer des graphiques SkiaSharp en Xamarin.Forms provoquant l’appel périodique de la PaintSurface
méthode, chaque fois que vous dessinez les graphiques différemment. Voici une animation présentée plus loin dans cet article avec des cercles concentriques qui semblent s’étendre à partir du centre :
La page Pulsating Ellipse dans l’exemple de programme anime les deux axes d’un ellipse afin qu’il semble pulser, et vous pouvez même contrôler le taux de cette pulsation. Le fichier PulsatingEllipsePage.xaml instancie a Xamarin.FormsSlider
et a Label
pour afficher la valeur actuelle du curseur. Il s’agit d’un moyen courant d’intégrer une SKCanvasView
vue à d’autres Xamarin.Forms vues :
<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>
Le fichier code-behind instancie un Stopwatch
objet pour servir d’horloge haute précision. Le OnAppearing
remplacement définit le pageIsActive
champ sur true
et appelle une méthode nommée AnimationLoop
. Le OnDisappearing
remplacement définit ce pageIsActive
champ sur 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;
}
La AnimationLoop
méthode démarre les Stopwatch
boucles, puis est pageIsActive
true
. Il s’agit essentiellement d’une « boucle infinie » alors que la page est active, mais elle n’entraîne pas le blocage du programme, car la boucle se termine par un appel à l’opérateurawait
, ce qui permet à Task.Delay
d’autres parties de la fonction de programme. L’argument pour Task.Delay
qu’il se termine après 1/30e seconde. Cela définit la fréquence d’images de l’animation.
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();
}
La while
boucle commence par obtenir une durée de cycle à partir du Slider
. Il s’agit d’une durée en secondes, par exemple 5. La deuxième instruction calcule une valeur pour t
le temps. Pour un cycleTime
de 5, t
passe de 0 à 1 toutes les 5 secondes. L’argument de la Math.Sin
fonction dans la deuxième instruction est comprise entre 0 et 2π toutes les 5 secondes. La Math.Sin
fonction retourne une valeur comprise entre 0 et 1 et 0, puis –1 et 0 toutes les 5 secondes, mais avec des valeurs qui changent plus lentement lorsque la valeur est proche de 1 ou –1. La valeur 1 est ajoutée afin que les valeurs soient toujours positives, puis divisées par 2, de sorte que les valeurs varient de 1/2 à 1/2 à 0 à 1/2, mais plus lentes lorsque la valeur est d’environ 1 et 0. Il est stocké dans le scale
champ et il SKCanvasView
est invalidé.
La PaintSurface
méthode utilise cette scale
valeur pour calculer les deux axes de l’ellipse :
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);
}
}
La méthode calcule un rayon maximal en fonction de la taille de la zone d’affichage et d’un rayon minimal basé sur le rayon maximal. La scale
valeur est animée entre 0 et 1 et 0, de sorte que la méthode utilise cette méthode pour calculer une xRadius
plage comprise yRadius
entre minRadius
et maxRadius
. Ces valeurs sont utilisées pour dessiner et remplir un ellipse :
Notez que l’objet SKPaint
est créé dans un using
bloc. Comme de nombreuses classes SKPaint
SkiaSharp dérivent de SKObject
, qui dérive de SKNativeObject
, qui implémente l’interface IDisposable
. SKPaint
remplace la Dispose
méthode pour libérer des ressources non managées.
La mise SKPaint
en place d’un using
bloc garantit qu’elle Dispose
est appelée à la fin du bloc pour libérer ces ressources non managées. Cela se produit quand même lorsque la mémoire utilisée par l’objet SKPaint
est libérée par le garbage collector .NET, mais dans le code d’animation, il est préférable d’être proactif dans la libération de la mémoire de manière plus ordonnée.
Dans ce cas particulier, une meilleure solution consisterait à créer deux SKPaint
objets une seule fois et à les enregistrer sous forme de champs.
C’est ce que fait l’animation Des cercles de développement. La ExpandingCirclesPage
classe commence par définir plusieurs champs, y compris un SKPaint
objet :
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;
}
...
}
Ce programme utilise une approche différente de l’animation basée sur la Xamarin.FormsDevice.StartTimer
méthode. Le t
champ est animé de 0 à 1 toutes les cycleTime
millisecondes :
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;
}
...
}
Le PaintSurface
gestionnaire dessine cinq cercles concentriques avec des rayons animés. Si la baseRadius
variable est calculée comme étant 100, comme t
elle est animée de 0 à 1, le rayon des cinq cercles passe de 0 à 100, 100 à 200, 200 à 300, 300 à 400 et 400 à 500. Pour la plupart des cercles, la valeur strokeWidth
est de 50, mais pour le premier cercle, les strokeWidth
anime de 0 à 50. Pour la plupart des cercles, la couleur est bleue, mais pour le dernier cercle, la couleur est animée de bleu à transparent. Notez le quatrième argument du SKColor
constructeur qui spécifie 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);
}
}
}
Le résultat est que l’image ressemble au même quand t
elle est égale à 0 que lorsqu’elle t
est égale à 1, et les cercles semblent continuer à s’étendre pour toujours :