Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Узнайте, как анимировать графику 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, являются производными от SKObject
SKNativeObject
, от которых реализуется 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);
}
}
}
Результатом является то, что изображение выглядит так же, как t
t
если равно 1, и круги, кажется, продолжают расширяться навсегда: