Aracılığıyla paylaş


SkiaSharp'ta Temel Animasyon

SkiaSharp grafiklerinize animasyon ekleme

SkiaSharp grafiklerine Xamarin.Forms animasyon eklemek için yöntemin PaintSurface düzenli aralıklarla çağrılmasına ve grafiklerin biraz farklı çizilmesine neden olabilirsiniz. Bu makalenin devamında gösterilen ve merkezden genişleyen eşmerkezli daireler içeren bir animasyon aşağıda verilmiştir:

Merkezden genişleyen birkaç eşmerkezli daire

Örnek programdaki Pulsating Ellipse sayfası, üç noktanın iki eksenine animasyon ekler, böylece titrek gibi görünür ve hatta bu pulsasyonun hızını denetleyebilirsiniz. PulsatingEllipsePage.xaml dosyası, kaydırıcının geçerli değerini görüntülemek için bir LabelXamarin.FormsSlider ve örneği oluşturur. Bu, bir SKCanvasView öğesini diğer Xamarin.Forms görünümlerle tümleştirmenin yaygın bir yoludur:

<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>

Arka plan kod dosyası, yüksek duyarlıklı bir saat görevi görecek bir Stopwatch nesnenin örneğini oluşturur. Geçersiz OnAppearing kılma alanı pageIsActive olarak true ayarlar ve adlı AnimationLoopbir yöntemi çağırır. Geçersiz kılma bu OnDisappearingpageIsActive alanı olarak falseayarlar:

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;
}

AnimationLoop yöntemi öğesini başlatır Stopwatch ve ardından olduğu sırada pageIsActive döngüye başlartrue. Sayfa etkinken bu temelde bir "sonsuz döngüdür", ancak döngü işleç ile çağrısıyla Task.Delayawait sonuçlandığından programın askıda kalmamasına neden olmaz ve bu da program işlevinin diğer bölümlerini sağlar. bağımsız değişkeni Task.Delay , 1/30 saniye sonra tamamlanmasına neden olur. Bu, animasyonun kare hızını tanımlar.

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();
}

Döngü, while 'den Sliderbir döngü süresi elde ederek başlar. Bu, saniye olarak bir süredir, örneğin 5. İkinci deyim, saat değerini hesaplart. cycleTime 5'ten 5 t saniyede bir 0'dan 1'e çıkar. İkinci deyimdeki işlevin Math.Sin bağımsız değişkeni her 5 saniyede bir 0 ile 2π arasında değişir. İşlev, Math.Sin 0 ile 1 arasında 0 ile 0 arasında ve sonra her 5 saniyede bir –1 ve 0 arasında bir değer döndürür, ancak değer 1 veya –1'e yaklaştığında daha yavaş değişen değerlerle birlikte. 1 değeri eklenir, böylece değerler her zaman pozitif olur ve sonra 2'ye bölünür, bu nedenle değerler 1/2 ile 1-1/2 ile 0 ile 1/2 arasında değişir, ancak değer 1 ve 0 civarında olduğunda daha yavaş olur. Bu, alanında scale depolanır ve SKCanvasView geçersiz kılındı.

yöntemi üç PaintSurface noktanın iki eksenini hesaplamak için bu scale değeri kullanır:

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);
    }
}

yöntemi, görüntüleme alanının boyutuna göre maksimum yarıçapı ve maksimum yarıçapa göre en düşük yarıçapı hesaplar. scale Değer 0 ile 1 arasında ve 0'a geri animasyonludur, bu nedenle yöntemi bunu kullanarak ve arasında minRadiusmaxRadiusdeğişen bir xRadius ve yRadius değerini hesaplar. Bu değerler üç nokta çizmek ve doldurmak için kullanılır:

Pulsating Ellipse sayfasının üçlü ekran görüntüsü

Nesnenin SKPaint bir using blokta oluşturulduğuna dikkat edin. Birçok SkiaSharp sınıfı SKPaintSKObjectda arabirimini uygulayan IDisposable 'dan SKNativeObjecttüretilir. SKPaintDispose yönetilmeyen kaynakları serbest bırakmak için yöntemini geçersiz kılar.

Bir using bloğun içine koymakSKPaint, Dispose bu yönetilmeyen kaynakları boşaltmak için bloğun sonunda çağrılmasını sağlar. Nesne tarafından SKPaint kullanılan bellek .NET çöp toplayıcısı tarafından serbest olduğunda yine de bu durum ortaya çıkar, ancak animasyon kodunda, belleği daha düzenli bir şekilde serbest bırakırken proaktif olmak en iyisidir.

Bu durumda daha iyi bir çözüm, bir kez iki SKPaint nesne oluşturmak ve bunları alan olarak kaydetmektir.

Genişleyen Daireler animasyonu bunu yapar. sınıfı, ExpandingCirclesPage bir SKPaint nesne de dahil olmak üzere birkaç alan tanımlayarak başlar:

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;
    }
    ...
}

Bu program yöntemine göre Xamarin.FormsDevice.StartTimer animasyon için farklı bir yaklaşım kullanır. Alan t her cycleTime milisaniyede 0 ile 1 arasında animasyonludur:

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;
    }
    ...
}

İşleyici, PaintSurface animasyonlu yarıçaplı beş eşmerkezli daire çizer. baseRadius Değişken 100 olarak hesaplanırsa, 0'dan 1'e animasyonlu t olarak, beş dairenin yarıçapı 0'dan 100'e, 100'den 200'e, 200'den 300'e, 300'den 400'e ve 400'den 500'e çıkar. Dairelerin strokeWidth çoğu için 50'dir, ancak ilk daire strokeWidth için animasyon 0 ile 50 arasında olur. Dairelerin çoğu için renk mavidir, ancak son daire için renk maviden saydama animasyonludur. Oluşturucunun SKColor opaklığı belirten dördüncü bağımsız değişkenine dikkat edin:

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);
        }
    }
}

Sonuç olarak, görüntü 0'a eşit olduğunda t 1'e eşit olduğunda t aynı görünür ve daireler sonsuza kadar genişlemeye devam eder:

Genişleten Daireler sayfasının üçlü ekran görüntüsü