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 do 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), o 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 2π radianos, 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 como no 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 translaçã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
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 centralizado em torno de um ponto de pivô especificado usando estas versões dos RotateDegrees
métodos and 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 acontece com 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 é equivalente 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 Centrada ;
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 um não centralizado 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 por DrawText
no 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 cancelam:
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 à forma como 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, existem várias maneiras de combinar rotação e translação. A página Texto girado cria a seguinte exibição:
Aqui está o PaintSurface
RotatedTextPage
manipulador da 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 deslocado disso. Esse valor é a coordenada Y necessária para posicionar o texto de forma que ele fique realmente centralizado verticalmente na página. O for
loop então 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 pareça um dodecago.
Uma maneira de simplificar esse código é incrementar o ângulo de rotação em 30 graus a cada vez por meio 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 . Agora, o DrawText
call center do texto verticalmente na parte superior da tela.
Como as transformações são aplicadas conceitualmente de forma oposta à forma como aparecem no código, geralmente é possível começar com transformações mais globais, seguidas por transformações mais locais. Geralmente, essa é 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 em torno desse canto. Em seguida, translade 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 de Download gratuito em PDF de Criando aplicativos móveis com Xamarin.Forms) A OnAppearing
substituição cria dois Animation
objetos com métodos de retorno de chamada e, em seguida, os chama Commit
por 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 é revolveDegrees
animado de 0 graus a 360 graus em 10 segundos. O segundo é animado rotateDegrees
de 0 graus a 360 graus a cada 1 segundo e também invalida a superfície para gerar outra chamada para o 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 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ário baseado em um círculo centralizado no ponto (0, 0) com um raio de 100. Ele usa translação e dimensionamento para expandir e centralizar esse círculo na página.
As Translate
chamadas and Scale
se aplicam globalmente ao relógio, portanto, 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 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 escala. 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);
}
...
}
}
Por fim, o PaintSurface
manipulador obtém a hora atual e calcula os graus de rotação para os ponteiros de horas, minutos e segundos. Cada mão é desenhada na posição 12:00 para que o ângulo de rotação seja relativo a isso:
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 rudimentares:
Para um relógio mais atraente, consulte o artigo Dados de caminho SVG no SkiaSharp.