Sdílet prostřednictvím


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:

Několik soustředných kruhů zdánlivě se zvětšující 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:

Trojitý snímek obrazovky se stránkou Pulsating Ellipse

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 SKNativeObjectSKObject, 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í:

Trojitý snímek obrazovky se stránkou Rozbalení kruhů