Základní animace ve SkiaSharpu
Zjistěte, jak animovat grafiku SkiaSharp
Grafiku Xamarin.Forms SkiaSharp můžete animovat tak PaintSurface
, že způsob, který se bude pravidelně volat, při každém kreslení grafiky trochu jinak. Tady je animace zobrazená dále v tomto článku se soustřednými kruhy, které se zdánlivě rozšiřují ze středu:
Stránka Pulzující elipse v ukázkovém programu animuje dvě osy elipsy tak, aby se zdá být pulzující, a můžete dokonce řídit rychlost této pulsace. Soubor PulseatingEllipsePage.xaml vytvoří Xamarin.FormsSlider
instanci a a Label
zobrazí aktuální hodnotu posuvníku. Toto je běžný způsob integrace SKCanvasView
s jinými Xamarin.Forms zobrazeními:
<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>
Soubor kódu vytvoří instanci objektu Stopwatch
tak, aby sloužil jako hodiny s vysokou přesností. Přepsání OnAppearing
nastaví pageIsActive
pole na true
a zavolá metodu s názvem AnimationLoop
. Přepsání OnDisappearing
nastaví toto 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
spustí Stopwatch
a pak smyčky zatímco pageIsActive
je true
. Jedná se v podstatě o "nekonečnou smyčku", když je stránka aktivní, ale nezpůsobí, že program přestane reagovat, protože smyčka končí voláním Task.Delay
s operátorem await
, který umožňuje další části funkce programu. Argument, který Task.Delay
způsobí, že se dokončí po 1/30. sekundě. Tím se definuje frekvence snímků animace.
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();
}
Smyčka while
začíná získáním doby cyklu od Slider
. Jedná se například o čas v sekundách, například 5. Druhý příkaz vypočítá hodnotu t
času. cycleTime
U 5 t
se zvyšuje od 0 do 1 každých 5 sekund. Argument funkce Math.Sin
ve druhém příkazu se pohybuje od 0 do 2π každých 5 sekund. Funkce Math.Sin
vrátí hodnotu od 0 do 1 zpět do 0 a potom do –1 a 0 každých 5 sekund, ale s hodnotami, které se mění pomaleji, když se hodnota blíží 1 nebo –1. Hodnota 1 se přičítá, takže hodnoty jsou vždy kladné a pak jsou dělené 2, takže hodnoty jsou v rozsahu od 1/2 do 1/2 až 0 až 1/2, ale pomalejší, pokud je hodnota kolem 1 a 0. Toto je uloženo v scale
poli a SKCanvasView
je zneplatněný.
Metoda PaintSurface
používá tuto scale
hodnotu k výpočtu dvou os tří teček:
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 vypočítá maximální poloměr na základě velikosti oblasti zobrazení a minimální poloměr na základě maximálního poloměru. Hodnota scale
je animovaný mezi 0 a 1 a zpět na 0, takže metoda používá k výpočtu xRadius
a yRadius
rozsahu mezi minRadius
a maxRadius
. Tyto hodnoty slouží k kreslení a vyplnění tří teček:
Všimněte si, že objekt SKPaint
je vytvořen v using
bloku. Stejně jako mnoho SkiaSharp třídy SKPaint
odvozen z , který je odvozen z SKNativeObject
SKObject
, který implementuje IDisposable
rozhraní. SKPaint
přepíše metodu Dispose
uvolnění nespravovaných prostředků.
Vložení SKPaint
bloku using
zajistí, že Dispose
se zavolá na konci bloku, aby se tyto nespravované prostředky uvolnily. K tomu dochází i přesto, když je paměť používaná objektem SKPaint
uvolněna uvolňováním paměti .NET, ale v animačním kódu je nejlepší být proaktivní při uvolňování paměti řadnějším způsobem.
Lepším řešením v tomto konkrétním případě by bylo vytvořit dva SKPaint
objekty jednou a uložit je jako pole.
To je to, co dělá animace Rozbalení kruhů . Třída ExpandingCirclesPage
začíná definováním několika polí, včetně objektu SKPaint
:
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;
}
...
}
Tento program používá jiný přístup k animaci na Xamarin.FormsDevice.StartTimer
základě metody. Pole t
je animované od 0 do 1 každých cycleTime
milisekund:
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;
}
...
}
Obslužná rutina PaintSurface
nakreslí pět soustředných kruhů s animovanými paprsky. baseRadius
Pokud se proměnná vypočítá jako 100, pak jak t
je animovaný od 0 do 1, radii pěti kruhů se zvětší z 0 na 100, 100 až 200, 200 na 300, 300 až 400 a 400 na 500. Pro většinu kruhů strokeWidth
je 50, ale pro první kruh, strokeWidth
animace od 0 do 50. U většiny kruhů je barva modrá, ale u posledního kruhu se barva animuje z modré na průhlednou. Všimněte si čtvrtého argumentu konstruktoru SKColor
, který určuje neprůhlednost:
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);
}
}
}
Výsledkem je, že obrázek vypadá stejně, když t
se rovná 0, jako když t
se rovná 1, a kruhy se zdá, že se neustále zvětšují: