Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Узнайте, как анимировать графику SkiaSharp
Вы можете анимировать графику Xamarin.Forms SkiaSharp, вызывая PaintSurface метод периодически, каждый раз при рисовании графики немного иначе. Ниже приведена анимация, показанная далее в этой статье с концентрическими кругами, которые, казалось бы, расширяются с центра:

Страница Pulsating Ellipse в примере программы анимирует две оси многоточия, чтобы она, как представляется, пульсирует, и вы даже можете контролировать частоту этой пульсации. Файл PulsatingEllipsePage.xaml создает экземпляр и Xamarin.FormsSlider отображает Label текущее значение ползунка. Это распространенный способ интеграции SKCanvasView с другими Xamarin.Forms представлениями:
<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 задает pageIsActive поле true и вызывает метод с именем 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 , а затем циклы во время pageIsActive выполнения true. Это по сути "бесконечный цикл" во время активности страницы, но это не приводит к зависанию программы, так как цикл завершается вызовом Task.Delay оператора await , который позволяет другим частям функции программы. Аргумент, который Task.Delay вызывает его завершение после 1/30 секунды. Это определяет частоту кадров анимации.
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 времени. cycleTime Для 5 увеличивается t от 0 до 1 каждые 5 секунд. Аргумент Math.Sin функции во втором операторе составляет от 0 до 2π каждые 5 секунд. Функция Math.Sin возвращает значение от 0 до 1 обратно до 0, а затем до –1 и 0 каждые 5 секунд, но со значениями, которые изменяются медленнее, когда значение почти 1 или –1. Значение 1 добавляется так, что значения всегда положительны, а затем делятся на 2, поэтому значения варьируются от 1/2 до 1/2 до 0 до 1/2, но медленнее, когда значение составляет около 1 и 0. Он хранится в scale поле и SKCanvasView является недействительным.
Метод PaintSurface использует это scale значение для вычисления двух осей многоточия:
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, поэтому метод использует его для вычисления xRadius и yRadius диапазонов между minRadius и maxRadius. Эти значения используются для рисования и заполнения многоточия:
Обратите внимание, что SKPaint объект создается в блоке using . Как и многие классы SKPaint SkiaSharp, являются производными от SKObjectSKNativeObject, от которых реализуется IDisposable интерфейс. SKPaint переопределяет Dispose метод, чтобы освободить неуправляемые ресурсы.
Добавление SKPaint в using блок гарантирует, что 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 анимировано от 0 до 1 миллисекунда cycleTime :
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 рисует пять концентрических кругов с анимированными радиями. baseRadius Если переменная вычисляется как 100, то как t анимация от 0 до 1, радии пяти кругов увеличивается от 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);
}
}
}
Результатом является то, что изображение выглядит так же, как tt если равно 1, и круги, кажется, продолжают расширяться навсегда:

