Trasformazione di rotazione
Esplora gli effetti e le animazioni possibili con la trasformazione Ruota skiaSharp
Con la trasformazione di rotazione, gli oggetti grafici SkiaSharp interrompono il vincolo di allineamento con gli assi orizzontali e verticali:
Per ruotare un oggetto grafico intorno al punto (0, 0), SkiaSharp supporta sia un RotateDegrees
metodo che un RotateRadians
metodo:
public void RotateDegrees (Single degrees)
public Void RotateRadians (Single radians)
Un cerchio di 360 gradi è uguale a 2π radianti, quindi è facile convertire tra le due unità. Usare qualsiasi opzione sia utile. Tutte le funzioni trigonometriche nella classe .NET Math
usano unità di radianti.
La rotazione è in senso orario per gli angoli crescenti. Anche se la rotazione sul sistema di coordinate cartesiano è antiorario per convenzione, la rotazione in senso orario è coerente con le coordinate Y che aumentano in modo crescente come in SkiaSharp. Sono consentiti angoli negativi e angoli maggiori di 360 gradi.
Le formule di trasformazione per la rotazione sono più complesse di quelle per la conversione e la scalabilità. Per un angolo di α, le formule di trasformazione sono:
x' = x•cos(α) – y•sin(α)
y' = x•sin(α) + y•cos(α)
La pagina Rotazione di base illustra il RotateDegrees
metodo . Il file BasicRotate.xaml.cs visualizza del testo con la linea di base allineata al centro della pagina e la ruota in base a un Slider
oggetto con un intervallo compreso tra -360 e 360. Ecco la parte pertinente del PaintSurface
gestore:
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);
}
Poiché la rotazione è allineata al centro dell'angolo superiore sinistro dell'area di disegno, per la maggior parte degli angoli impostati in questo programma, il testo viene ruotato sullo schermo:
Molto spesso è consigliabile ruotare un elemento centrato intorno a un punto pivot specificato usando queste versioni dei RotateDegrees
metodi e RotateRadians
:
public void RotateDegrees (Single degrees, Single px, Single py)
public void RotateRadians (Single radians, Single px, Single py)
La pagina Ruota al centro è simile alla rotazione di base, ad eccezione del fatto che la versione espansa di RotateDegrees
viene usata per impostare il centro di rotazione sullo stesso punto usato per posizionare il testo:
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);
}
Ora il testo ruota intorno al punto usato per posizionare il testo, ovvero il centro orizzontale della linea di base del testo:
Come per la versione centrata del Scale
metodo, la versione centrata della RotateDegrees
chiamata è un collegamento. Ecco il metodo :
RotateDegrees (degrees, px, py);
Tale chiamata equivale a quanto segue:
canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);
Si scoprirà che a volte è possibile combinare Translate
le chiamate con Rotate
le chiamate. Ad esempio, ecco le RotateDegrees
chiamate e DrawText
nella pagina Ruota al centro;
canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
La RotateDegrees
chiamata equivale a due Translate
chiamate e a un non centrato 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);
La DrawText
chiamata per visualizzare il testo in una determinata posizione equivale a una Translate
chiamata per tale posizione seguita dal DrawText
punto (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);
Le due chiamate consecutive Translate
si annullano l'una dall'altra:
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);
Concettualmente, le due trasformazioni vengono applicate nell'ordine opposto a come appaiono nel codice. La DrawText
chiamata visualizza il testo nell'angolo superiore sinistro dell'area di disegno. La RotateDegrees
chiamata ruota il testo rispetto all'angolo superiore sinistro. La chiamata sposta quindi Translate
il testo al centro dell'area di disegno.
Esistono in genere diversi modi per combinare rotazione e traslazione. La pagina Testo ruotato crea la visualizzazione seguente:
Ecco il PaintSurface
gestore della 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();
}
}
}
I xCenter
valori e yCenter
indicano il centro dell'area di disegno. Il yText
valore è un piccolo offset rispetto a quello. Questo valore è la coordinata Y necessaria per posizionare il testo in modo che sia realmente centrato verticalmente nella pagina. Il for
ciclo imposta quindi una rotazione in base al centro dell'area di disegno. La rotazione è in incrementi di 30 gradi. Il testo viene disegnato utilizzando il yText
valore . Il numero di spazi vuoti prima della parola "ROTATE" nel text
valore è stato determinato empiricamente per stabilire la connessione tra queste 12 stringhe di testo sembra essere un dodecagon.
Un modo per semplificare questo codice consiste nell'incrementare l'angolo di rotazione di 30 gradi ogni volta che si passa attraverso il ciclo dopo la DrawText
chiamata. In questo modo si elimina la necessità di chiamate a Save
e Restore
. Si noti che la degrees
variabile non viene più usata all'interno del corpo del for
blocco:
for (int degrees = 0; degrees < 360; degrees += 30)
{
canvas.DrawText(text, xCenter, yText, textPaint);
canvas.RotateDegrees(30, xCenter, yCenter);
}
È anche possibile usare la forma semplice di RotateDegrees
anteponendo il ciclo con una chiamata a per Translate
spostare tutto al centro dell'area di disegno:
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);
}
Il calcolo modificato yText
non incorpora yCenter
più . Ora il call center il DrawText
testo verticalmente nella parte superiore dell'area di disegno.
Poiché le trasformazioni vengono applicate concettualmente opposte a come vengono visualizzate nel codice, spesso è possibile iniziare con più trasformazioni globali, seguite da più trasformazioni locali. Questo è spesso il modo più semplice per combinare rotazione e traslazione.
Si supponga, ad esempio, di voler disegnare un oggetto grafico che ruota intorno al suo centro in modo molto simile a un pianeta che ruota sull'asse. Ma vuoi anche che questo oggetto ruota intorno al centro dello schermo molto simile a un pianeta che ruota intorno al sole.
È possibile eseguire questa operazione posizionando l'oggetto nell'angolo superiore sinistro dell'area di disegno e quindi usando un'animazione per ruotarla attorno a tale angolo. Successivamente, traslare l'oggetto orizzontalmente come un raggio orbitale. Applicare ora una seconda rotazione animata, anche intorno all'origine. In questo modo l'oggetto ruota attorno all'angolo. Convertire ora al centro dell'area di disegno.
Ecco il PaintSurface
gestore che contiene queste chiamate di trasformazione in ordine inverso:
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);
}
}
I revolveDegrees
campi e rotateDegrees
vengono animati. Questo programma usa una tecnica di animazione diversa basata sulla Xamarin.FormsAnimation
classe . Questa classe è descritta nel capitolo 22 di Download pdf gratuito di Creazione di app per dispositivi mobili con Xamarin.Forms) L'override OnAppearing
crea due Animation
oggetti con metodi di callback e quindi li chiama Commit
per una durata di animazione:
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);
}
Il primo Animation
oggetto si anima revolveDegrees
da 0 gradi a 360 gradi oltre 10 secondi. Il secondo anima rotateDegrees
da 0 a 360 gradi ogni 1 secondo e invalida anche la superficie per generare un'altra chiamata al PaintSurface
gestore. L'override OnDisappearing
annulla queste due animazioni:
protected override void OnDisappearing()
{
base.OnDisappearing();
this.AbortAnimation("revolveAnimation");
this.AbortAnimation("rotateAnimation");
}
Il programma Clock analogico brutto (così chiamato perché un orologio analogico più attraente verrà descritto in un articolo successivo) usa la rotazione per disegnare i segni minuti e ore dell'orologio e per ruotare le mani. Il programma disegna l'orologio utilizzando un sistema di coordinate arbitrario basato su un cerchio centrato al punto (0, 0) con un raggio di 100. Usa la traduzione e il ridimensionamento per espandere e allineare il cerchio nella pagina.
Le Translate
chiamate e Scale
si applicano a livello globale all'orologio, quindi queste sono le prime da chiamare dopo l'inizializzazione degli SKPaint
oggetti:
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));
...
}
}
Ci sono 60 segni di due diverse dimensioni che devono essere disegnate in un cerchio intorno all'orologio. La DrawCircle
chiamata disegna tale cerchio nel punto (0, -90), che rispetto al centro dell'orologio corrisponde alle 12:00. La RotateDegrees
chiamata incrementa l'angolo di rotazione di 6 gradi dopo ogni segno di graduazione. La angle
variabile viene usata esclusivamente per determinare se viene disegnato un cerchio grande o un piccolo cerchio:
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);
}
...
}
}
Infine, il PaintSurface
gestore ottiene l'ora corrente e calcola i gradi di rotazione per l'ora, il minuto e le mani seconde. Ogni mano viene disegnata nella posizione 12:00 in modo che l'angolo di rotazione sia relativo a quanto segue:
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();
}
}
L'orologio è certamente funzionale anche se le mani sono piuttosto grezze:
Per un orologio più interessante, vedere l'articolo SVG Path Data in SkiaSharp.