A transformação de rotação
Explore os efeitos e animações possíveis com a transformação de rotação SkiaSharp
Com a transformação de rotação, os objetos gráficos SkiaSharp se libertam da restrição de alinhamento com os eixos horizontal e vertical:
Para girar um objeto gráfico em torno do ponto (0, 0), SkiaSharp suporta um RotateDegrees
método e um RotateRadians
método:
public void RotateDegrees (Single degrees)
public Void RotateRadians (Single radians)
Um círculo de 360 graus é o mesmo que radianos 2π, por isso é fácil converter entre as duas unidades. Use o que for conveniente. Todas as funções trigonométricas na classe .NET Math
usam unidades de radianos.
A rotação é no sentido horário para aumentar os ângulos. (Embora a rotação no sistema de coordenadas cartesianas seja no sentido anti-horário por convenção, a rotação no sentido horário é consistente com as coordenadas Y aumentando descendo como em SkiaSharp.) Ângulos negativos e ângulos maiores que 360 graus são permitidos.
As fórmulas de transformação para rotação são mais complexas do que aquelas para tradução e escala. Para um ângulo de α, as fórmulas de transformação são:
x' = x•cos(α) – y•sin(α)
y' = x•sin(α) + y•cos(α)
A página Rotação básica demonstra o RotateDegrees
método. O arquivo BasicRotate.xaml.cs exibe algum texto com sua linha de base centralizada na página e o gira com base em um Slider
com um intervalo de –360 a 360. Aqui está a parte relevante do PaintSurface
manipulador:
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);
}
Como a rotação é centralizada em torno do canto superior esquerdo da tela, para a maioria dos ângulos definidos neste programa, o texto é girado para fora da tela:
Muitas vezes, você desejará girar algo centrado em torno de um ponto de pivô especificado usando estas versões dos RotateDegrees
métodos e RotateRadians
:
public void RotateDegrees (Single degrees, Single px, Single py)
public void RotateRadians (Single radians, Single px, Single py)
A página Rotação centralizada é exatamente como a rotação básica, exceto que a versão expandida do RotateDegrees
é usada para definir o centro de rotação para o mesmo ponto usado para posicionar o texto:
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);
}
Agora, o texto gira em torno do ponto usado para posicionar o texto, que é o centro horizontal da linha de base do texto:
Assim como a versão centralizada do Scale
método, a versão centralizada da RotateDegrees
chamada é um atalho. Aqui está o método:
RotateDegrees (degrees, px, py);
Essa chamada equivale ao seguinte:
canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);
Você descobrirá que, às vezes, pode combinar Translate
chamadas com Rotate
chamadas. Por exemplo, aqui estão as RotateDegrees
chamadas e DrawText
na página Rotação centralizada;
canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
A RotateDegrees
chamada é equivalente a duas Translate
chamadas e uma não centralizada 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);
A DrawText
chamada para exibir texto em um local específico é equivalente a uma Translate
chamada para esse local seguida pelo DrawText
ponto (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);
As duas chamadas consecutivas Translate
se anulam:
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);
Conceitualmente, as duas transformações são aplicadas na ordem oposta à que aparecem no código. A DrawText
chamada exibe o texto no canto superior esquerdo da tela. A RotateDegrees
chamada gira esse texto em relação ao canto superior esquerdo. Em seguida, a Translate
chamada move o texto para o centro da tela.
Geralmente, há várias maneiras de combinar rotação e translação. A página Texto Girado cria a seguinte exibição:
Aqui está o PaintSurface
manipulador da RotatedTextPage
classe:
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();
}
}
}
Os xCenter
valores e yCenter
indicam o centro da tela. O yText
valor é um pouco compensado disso. Esse valor é a coordenada Y necessária para posicionar o texto de modo que ele fique verdadeiramente centralizado verticalmente na página. Em for
seguida, o loop define uma rotação com base no centro da tela. A rotação é em incrementos de 30 graus. O texto é desenhado usando o yText
valor. O número de espaços em branco antes da palavra "ROTATE" no text
valor foi determinado empiricamente para fazer com que a conexão entre essas 12 cadeias de texto parecesse ser um dodecagon.
Uma maneira de simplificar esse código é incrementar o ângulo de rotação em 30 graus cada vez através do loop após a DrawText
chamada. Isso elimina a necessidade de chamadas para Save
e Restore
. Observe que a degrees
variável não é mais usada no corpo do for
bloco:
for (int degrees = 0; degrees < 360; degrees += 30)
{
canvas.DrawText(text, xCenter, yText, textPaint);
canvas.RotateDegrees(30, xCenter, yCenter);
}
Também é possível usar a forma simples de RotateDegrees
prefaciar o loop com uma chamada para Translate
mover tudo para o centro da tela:
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);
}
O cálculo modificado yText
não incorpora yCenter
mais o . Agora, a chamada centraliza DrawText
o texto verticalmente na parte superior da tela.
Como as transformações são conceitualmente aplicadas de forma oposta à forma como aparecem no código, muitas vezes é possível começar com transformações mais globais, seguidas por transformações mais locais. Esta é muitas vezes a maneira mais fácil de combinar rotação e translação.
Por exemplo, suponha que você queira desenhar um objeto gráfico que gira em torno de seu centro como um planeta girando em seu eixo. Mas você também quer que esse objeto gire em torno do centro da tela como um planeta girando em torno do Sol.
Você pode fazer isso posicionando o objeto no canto superior esquerdo da tela e, em seguida, usando uma animação para girá-lo ao redor desse canto. Em seguida, traduza o objeto horizontalmente como um raio orbital. Agora aplique uma segunda rotação animada, também em torno da origem. Isso faz com que o objeto gire ao virar da esquina. Agora traduza para o centro da tela.
Aqui está o PaintSurface
manipulador que contém essas chamadas de transformação na ordem inversa:
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);
}
}
Os revolveDegrees
campos e rotateDegrees
são animados. Este programa usa uma técnica de animação diferente com base na Xamarin.FormsAnimation
classe. (Esta classe é descrita no Capítulo 22 do Download gratuito em PDF de Criando aplicativos móveis comXamarin.Forms ) A OnAppearing
substituição cria dois Animation
objetos com métodos de retorno de chamada e, em seguida, os chama Commit
para uma duração de animação:
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);
}
O primeiro Animation
objeto anima revolveDegrees
de 0 graus a 360 graus em 10 segundos. O segundo anima rotateDegrees
de 0 graus a 360 graus a cada 1 segundo e também invalida a superfície para gerar outra chamada ao PaintSurface
manipulador. A OnDisappearing
substituição cancela estas duas animações:
protected override void OnDisappearing()
{
base.OnDisappearing();
this.AbortAnimation("revolveAnimation");
this.AbortAnimation("rotateAnimation");
}
O programa Ugly Analog Clock (assim chamado porque um relógio analógico mais atraente será descrito em um artigo posterior) usa a rotação para desenhar as marcas de minutos e horas do relógio e girar os ponteiros. O programa desenha o relógio usando um sistema de coordenadas arbitrárias baseado em um círculo que está centralizado no ponto (0, 0) com um raio de 100. Ele usa tradução e dimensionamento para expandir e centralizar esse círculo na página.
As Translate
chamadas e Scale
se aplicam globalmente ao relógio, então essas são as primeiras a serem chamadas após a inicialização dos SKPaint
objetos:
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));
...
}
}
Existem 60 marcas de dois tamanhos diferentes que devem ser desenhadas em um círculo o tempo todo. A DrawCircle
chamada desenha esse círculo no ponto (0, –90), que em relação ao centro do relógio corresponde a 12:00. A RotateDegrees
chamada incrementa o ângulo de rotação em 6 graus após cada marca de seleção. A angle
variável é usada apenas para determinar se um círculo grande ou um círculo pequeno é desenhado:
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);
}
...
}
}
Finalmente, o PaintSurface
manipulador obtém a hora atual e calcula os graus de rotação para a hora, minuto e segundos ponteiros. Cada mão é desenhada na posição 12:00 para que o ângulo de rotação seja relativo àquele:
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();
}
}
O relógio é certamente funcional, embora os ponteiros sejam bastante grosseiros:
Para obter um relógio mais atraente, consulte o artigo Dados de caminho SVG no SkiaSharp.