Udostępnij za pośrednictwem


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:

Kilka koncentrycznych okręgów pozornie rozszerzających się z centrum

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:

Zrzut ekranu przedstawiający stronę Pulsating Ellipse

Zwróć uwagę, że SKPaint obiekt jest tworzony w using bloku. Podobnie jak wiele klas SKPaint SkiaSharp pochodzi z SKObjectklasy , która pochodzi z SKNativeObjectklasy , 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:

Potrójny zrzut ekranu przedstawiający stronę Rozwijanie okręgów