다음을 통해 공유


SkiaSharp의 기본 애니메이션

SkiaSharp 그래픽에 애니메이션 효과를 적용하는 방법 알아보기

그래픽 Xamarin.Forms 을 약간 다르게 그릴 때마다 메서드가 주기적으로 호출되도록 하여 PaintSurface SkiaSharp 그래픽에 애니메이션 효과를 주기적으로 적용할 수 있습니다. 다음은 이 문서의 뒷부분에 있는 가운데에서 겉보기에 확장되는 동심원을 사용하는 애니메이션입니다.

가운데에서 겉보기에 확장되는 여러 동심원

샘플 프로그램의 펄스 타원 페이지는 타원의 두 축에 애니메이션 효과를 주므로 맥동하는 것처럼 보이며 이 맥동 속도를 제어할 수도 있습니다. PulsatingEllipsePage.xaml 파일은 a 및 aSliderXamarin.FormsLabel 를 인스턴스화하여 슬라이더의 현재 값을 표시합니다. 이는 다른 Xamarin.Forms 뷰와 통합하는 SKCanvasView 일반적인 방법입니다.

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

코드 숨김 파일은 개체를 Stopwatch 인스턴스화하여 정밀도가 높은 시계 역할을 합니다. 재정의는 OnAppearing 필드를 truepageIsActive로 설정하고 명명AnimationLoop된 메서드를 호출합니다. 재정의는 OnDisappearing 해당 pageIsActive 필드를 다음으로 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;
}

메서드가 AnimationLoop 시작되고 Stopwatch 루프가 있는 동안 pageIsActivetrue반복됩니다. 이는 기본적으로 페이지가 활성화되어 있는 동안 "무한 루프"이지만 루프가 연산자를 호출 Task.Delayawait 하여 종료되므로 프로그램이 중단되지 않습니다. 그러면 프로그램 함수의 다른 부분이 작동할 수 있습니다. 1/30초 후에 완료되도록 하는 인수 Task.Delay 입니다. 애니메이션의 프레임 속도를 정의합니다.

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

루프는 while .에서 주기 시간을 가져오는 것으로 시작합니다 Slider. 시간(초)(예: 5)입니다. 두 번째 문은 시간 값을 t계산합니다. 5의 cycleTime 경우 5 t 초마다 0에서 1로 증가합니다. 두 번째 문의 함수에 대한 Math.Sin 인수 범위는 5초마다 0~2π입니다. 함수는 Math.Sin 0에서 1까지의 값을 반환한 다음 5초마다 –1 및 0까지의 값을 반환하지만 값이 1 또는 –1에 가까이면 값이 더 느리게 변경됩니다. 값 1이 추가되므로 값이 항상 양수이고 2로 나뉘므로 값 범위는 1/2에서 1/2에서 0에서 1/2까지이지만 값이 1과 0인 경우 속도가 느립니다. 이 값은 필드에 저장 scale 되고 SKCanvasView 무효화됩니다.

메서드는 이 PaintSurfacescale 값을 사용하여 타원의 두 축을 계산합니다.

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

이 메서드는 표시 영역의 크기에 따라 최대 반경을 계산하고 최대 반경을 기준으로 최소 반경을 계산합니다. 이 값은 scale 0에서 1 사이로 애니메이션이 적용되고 0으로 다시 애니메이션되므로 메서드는 이 값을 사용하여 해당 값을 계산하고 yRadius 그 범위와 사이의 minRadiusmaxRadius범위를 계산 xRadius 합니다. 이러한 값은 줄임표를 그리고 채우는 데 사용됩니다.

펄스 타원 페이지의 삼중 스크린샷

개체가 SKPaint 블록에 using 만들어집니다. 많은 SkiaSharp 클래스와 SKPaint 마찬가지로 인터페이스를 구현하는 파생 클래스에서 SKNativeObject파생SKObject됩니다IDisposable. SKPaint 는 관리되지 않는 리소스를 Dispose 해제하도록 메서드를 재정의합니다.

using 블록을 배치하면 SKPaint 블록 끝에 호출되어 Dispose 관리되지 않는 리소스를 해제할 수 있습니다. 이 문제는 개체에서 SKPaint 사용하는 메모리가 .NET 가비지 수집기에서 해제될 때 발생하지만 애니메이션 코드에서는 보다 질서 정연한 방식으로 메모리를 해제하는 것이 가장 좋습니다.

이 경우에 더 나은 해결 방법은 두 SKPaint 개체를 한 번 만들어 필드로 저장하는 것입니다.

이것이 바로 확장 원 애니메이션이 하는 일입니다. 클래스는 ExpandingCirclesPage 개체를 포함하여 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;
    }
    ...
}

이 프로그램은 메서드를 기반으로 Xamarin.FormsDevice.StartTimer 애니메이션에 다른 방법을 사용합니다. t 필드는 밀리초마다 cycleTime 0에서 1까지 애니메이션됩니다.

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

PaintSurface 처리기는 애니메이션된 반지름으로 5개의 동심원을 그립니다. 변수가 baseRadius 100으로 계산된 경우 0에서 1까지 애니메이션된 것처럼 t 5개 원의 반경이 0에서 100, 100에서 200, 200에서 300, 300에서 400, 400에서 500으로 증가합니다. 대부분의 원의 strokeWidth 경우 50이지만 첫 번째 원 strokeWidth 의 경우 0에서 50까지 애니메이션 효과를 줍니다. 대부분의 원에서 색은 파란색이지만 마지막 원의 경우 색이 파란색에서 투명으로 애니메이션됩니다. 불투명도를 지정하는 SKColor 생성자에 대한 네 번째 인수를 확인합니다.

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

결과는 이미지가 1과 같을 때 tt 와 0이면 동일하게 표시되고 원은 계속 계속 확장되는 것처럼 보입니다.

원 확장 페이지의 세 가지 스크린샷