Преобразование циклического сдвига
Изучение эффектов и анимаций с помощью преобразования SkiaSharp
При преобразовании поворота графические объекты SkiaSharp освобождаются от ограничения выравнивания с горизонтальными и вертикальными осями:
Для поворота графического объекта вокруг точки (0, 0), SkiaSharp поддерживает RotateDegrees
метод и RotateRadians
метод:
public void RotateDegrees (Single degrees)
public Void RotateRadians (Single radians)
Круг 360 градусов совпадает с 2π радианами, поэтому легко преобразовать между двумя единицами. Используйте любой удобный вариант. Все тригонометрические функции в классе .NET Math
используют единицы радианов.
Поворот по часовой стрелке для увеличения углов. (Хотя поворот в системе координат Декартиана по соглашению против часовой стрелки по соглашению, по часовой стрелке согласуется с координатами Y, увеличиваясь, как в SkiaSharp.) Допустимы отрицательные угол и угол, превышающие 360 градусов.
Формулы преобразования для поворота являются более сложными, чем для перевода и масштабирования. Для угла α формулы преобразования:
x' = x•cos(α) — y•sin(α)
y' = x•sin(α) + y•cos(α)
На странице "Базовый поворот" демонстрируется RotateDegrees
метод. Файл BasicRotate.xaml.cs отображает некоторый текст со своим базовым центром на странице и поворачивает его на Slider
основе диапазона от –360 до 360. Ниже приведена соответствующая часть обработчика PaintSurface
:
using (SKPaint textPaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Blue,
TextAlign = SKTextAlign.Center,
TextSize = 100
})
{
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}
Так как поворот находится в левом верхнем углу холста, для большинства углов, заданных в этой программе, текст поворачивается с экрана:
Очень часто требуется повернуть что-то по центру вокруг указанной точки сводных данных с помощью этих версий RotateDegrees
и RotateRadians
методов:
public void RotateDegrees (Single degrees, Single px, Single py)
public void RotateRadians (Single radians, Single px, Single py)
Страница поворота по центру похожа на базовую поворотную, за исключением того, что развернутая версия RotateDegrees
используется для задания центра поворота в ту же точку, которая используется для размещения текста:
using (SKPaint textPaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Blue,
TextAlign = SKTextAlign.Center,
TextSize = 100
})
{
canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}
Теперь текст поворачивается вокруг точки, используемой для размещения текста, который является горизонтальным центром базового плана текста:
Как и в центре версии метода, центрированная версия Scale
RotateDegrees
вызова является ярлыком. Ниже приведен метод.
RotateDegrees (degrees, px, py);
Этот вызов эквивалентен следующему:
canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);
Вы обнаружите, что иногда можно сочетать Translate
звонки с Rotate
вызовами. Например, ниже приведены RotateDegrees
DrawText
вызовы и вызовы на странице " Поворот по центру";
canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
Вызов RotateDegrees
эквивалентен двум Translate
вызовам и нецентрированных RotateDegrees
:
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
Вызов DrawText
отображения текста в определенном расположении эквивалентен Translate
вызову этого расположения, за которым следует DrawText
в точке (0, 0):
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawText(Title, 0, 0, textPaint);
Два последовательных Translate
вызова отменяют друг друга:
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);
Концептуально два преобразования применяются в порядке, противоположном тому, как они отображаются в коде. Вызов DrawText
отображает текст в левом верхнем углу холста. Вызов RotateDegrees
поворачивает этот текст относительно левого верхнего угла. Translate
Затем вызов перемещает текст в центр холста.
Обычно существует несколько способов объединения поворота и перевода. Страница "Повернутый текст" создает следующее отображение:
PaintSurface
Вот обработчик RotatedTextPage
класса:
static readonly string text = " ROTATE";
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint textPaint = new SKPaint
{
Color = SKColors.Black,
TextSize = 72
})
{
float xCenter = info.Width / 2;
float yCenter = info.Height / 2;
SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
float yText = yCenter - textBounds.Height / 2 - textBounds.Top;
for (int degrees = 0; degrees < 360; degrees += 30)
{
canvas.Save();
canvas.RotateDegrees(degrees, xCenter, yCenter);
canvas.DrawText(text, xCenter, yText, textPaint);
canvas.Restore();
}
}
}
И xCenter
yCenter
значения указывают центр холста. Это yText
значение немного смещается от этого. Это значение является координатой Y, необходимой для размещения текста таким образом, чтобы он действительно по вертикали по центру на странице. Затем цикл for
задает поворот на основе центра холста. Поворот увеличивается на 30 градусов. Текст рисуется с помощью yText
значения. Число пустых перед словом "ROTATE" в text
значении было определено эмпирически, чтобы сделать соединение между этими 12 текстовыми строками, как представляется, dodecagon.
Один из способов упрощения этого кода заключается в том, чтобы увеличить угол поворота на 30 градусов каждый раз через цикл после DrawText
вызова. Это устраняет потребность в Save
вызовах и Restore
. Обратите внимание, что degrees
переменная больше не используется в теле for
блока:
for (int degrees = 0; degrees < 360; degrees += 30)
{
canvas.DrawText(text, xCenter, yText, textPaint);
canvas.RotateDegrees(30, xCenter, yCenter);
}
Кроме того, можно использовать простую форму RotateDegrees
, префиксируя цикл вызовом для Translate
перемещения всего в центр холста:
float yText = -textBounds.Height / 2 - textBounds.Top;
canvas.Translate(xCenter, yCenter);
for (int degrees = 0; degrees < 360; degrees += 30)
{
canvas.DrawText(text, 0, yText, textPaint);
canvas.RotateDegrees(30);
}
Измененное yText
вычисление больше не включает yCenter
. Теперь вызывайте DrawText
текст по вертикали в верхней части холста.
Поскольку преобразования концептуально применяются напротив того, как они отображаются в коде, часто можно начать с более глобальных преобразований, а затем более локальных преобразований. Это часто самый простой способ объединить поворот и перевод.
Например, предположим, что вы хотите нарисовать графический объект, который поворачивается вокруг своего центра так же, как планета, вращающаяся на ее оси. Но вы также хотите, чтобы этот объект вращался вокруг центра экрана так же, как планета, вращающаяся вокруг солнца.
Это можно сделать, разместив объект в левом верхнем углу холста, а затем с помощью анимации, чтобы повернуть его вокруг этого угла. Затем преобразуйте объект по горизонтали, как орбитальный радиус. Теперь примените второй анимированный поворот, а также вокруг источника. Это делает объект вращающимся вокруг угла. Теперь преобразуем в центр холста.
Ниже приведен PaintSurface
обработчик, содержащий эти вызовы преобразования в обратном порядке:
float revolveDegrees, rotateDegrees;
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint fillPaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Red
})
{
// Translate to center of canvas
canvas.Translate(info.Width / 2, info.Height / 2);
// Rotate around center of canvas
canvas.RotateDegrees(revolveDegrees);
// Translate horizontally
float radius = Math.Min(info.Width, info.Height) / 3;
canvas.Translate(radius, 0);
// Rotate around center of object
canvas.RotateDegrees(rotateDegrees);
// Draw a square
canvas.DrawRect(new SKRect(-50, -50, 50, 50), fillPaint);
}
}
rotateDegrees
Анимированные revolveDegrees
поля. Эта программа использует другой метод анимации на Xamarin.FormsAnimation
основе класса. (Этот класс описан в главе 22 Бесплатная загрузка PDF-файла создания мобильных приложений сXamarin.Forms помощью OnAppearing
функции переопределения создает два Animation
объекта с методами обратного вызова, а затем вызывает Commit
их в течение длительности анимации:
protected override void OnAppearing()
{
base.OnAppearing();
new Animation((value) => revolveDegrees = 360 * (float)value).
Commit(this, "revolveAnimation", length: 10000, repeat: () => true);
new Animation((value) =>
{
rotateDegrees = 360 * (float)value;
canvasView.InvalidateSurface();
}).Commit(this, "rotateAnimation", length: 1000, repeat: () => true);
}
Первый Animation
объект анимирует revolveDegrees
от 0 до 360 градусов в течение 10 секунд. Второй один анимирует rotateDegrees
от 0 до 360 градусов каждые 1 секунду, а также делает поверхность недопустимой для создания другого вызова обработчика PaintSurface
. Переопределение OnDisappearing
отменяет эти две анимации:
protected override void OnDisappearing()
{
base.OnDisappearing();
this.AbortAnimation("revolveAnimation");
this.AbortAnimation("rotateAnimation");
}
Уродливые аналоговые часы (так называемые, потому что более привлекательные аналоговые часы будут описаны в более поздней статье) использует поворот для рисования минут и часовых меток часов и поворота рук. Программа рисует часы с помощью произвольной системы координат на основе круга, который находится в центре точки (0, 0) с радиусом 100. Он использует преобразование и масштабирование для расширения и центра этого круга на странице.
Scale
Вызовы Translate
применяются глобально к часам, поэтому они являются первыми, которые должны вызываться после инициализации SKPaint
объектов:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint strokePaint = new SKPaint())
using (SKPaint fillPaint = new SKPaint())
{
strokePaint.Style = SKPaintStyle.Stroke;
strokePaint.Color = SKColors.Black;
strokePaint.StrokeCap = SKStrokeCap.Round;
fillPaint.Style = SKPaintStyle.Fill;
fillPaint.Color = SKColors.Gray;
// Transform for 100-radius circle centered at origin
canvas.Translate(info.Width / 2f, info.Height / 2f);
canvas.Scale(Math.Min(info.Width / 200f, info.Height / 200f));
...
}
}
Существует 60 меток двух разных размеров, которые должны быть нарисованы в кругу круглосуточно. Вызов DrawCircle
рисует этот круг в точке (0, –90), что относительно центра часов соответствует 12:00. Вызов RotateDegrees
увеличивает угол поворота на 6 градусов после каждой галочки. Переменная angle
используется исключительно для определения того, рисуется ли большой круг или небольшой круг:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
...
// Hour and minute marks
for (int angle = 0; angle < 360; angle += 6)
{
canvas.DrawCircle(0, -90, angle % 30 == 0 ? 4 : 2, fillPaint);
canvas.RotateDegrees(6);
}
...
}
}
Наконец, PaintSurface
обработчик получает текущее время и вычисляет градусы поворота в течение часа, минуты и секунды. Каждая рука рисуется в позиции 12:00, чтобы угол поворота был относительно этого:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
...
DateTime dateTime = DateTime.Now;
// Hour hand
strokePaint.StrokeWidth = 20;
canvas.Save();
canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
canvas.DrawLine(0, 0, 0, -50, strokePaint);
canvas.Restore();
// Minute hand
strokePaint.StrokeWidth = 10;
canvas.Save();
canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
canvas.DrawLine(0, 0, 0, -70, strokePaint);
canvas.Restore();
// Second hand
strokePaint.StrokeWidth = 2;
canvas.Save();
canvas.RotateDegrees(6 * dateTime.Second);
canvas.DrawLine(0, 10, 0, -80, strokePaint);
canvas.Restore();
}
}
Часы, безусловно, функциональны, хотя руки довольно сырые:
Более привлекательные часы см. в статье SVG Path Data in SkiaSharp.