Obrót — przekształcenie
Eksplorowanie efektów i animacji możliwych za pomocą przekształcenia obracania SkiaSharp
Dzięki przekształceniu obróceniu obiekty graficzne SkiaSharp łamią się bez ograniczeń wyrównania do osi poziomych i pionowych:
W przypadku obracania obiektu graficznego wokół punktu (0, 0), SkiaSharp obsługuje zarówno metodę RotateDegrees
, jak i metodę RotateRadians
:
public void RotateDegrees (Single degrees)
public Void RotateRadians (Single radians)
Okrąg 360 stopni jest taki sam jak 2 radianyπ, więc łatwo jest przekonwertować między dwiema jednostkami. Użyj niezależnie od tego, co jest wygodne. Wszystkie funkcje trygonometryczne w klasie .NET Math
używają jednostek radianów.
Obrót jest zgodnie z ruchem wskazówek zegara w celu zwiększenia kątów. (Mimo że rotacja układu współrzędnych kartezjańskich jest przeciwdługorowa zgodnie z konwencją, obrót zgodnie z ruchem wskazówek zegara jest zgodny ze współrzędnymi Y zwiększającymi się jak w SkiaSharp). Dozwolone są kąty ujemne i kąty większe niż 360 stopni.
Formuły przekształcania dla rotacji są bardziej złożone niż te do tłumaczenia i skalowania. Dla kąta α formuły przekształcania to:
x' = x•cos(α) – y•sin(α)
y' = x•sin(α) + y•cos(α)
Na stronie Rotacja podstawowa przedstawiono metodę RotateDegrees
. Plik BasicRotate.xaml.cs wyświetla jakiś tekst z punktem odniesienia wyśrodkowany na stronie i obraca go na Slider
podstawie zakresu od –360 do 360. Oto odpowiednia PaintSurface
część procedury obsługi:
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);
}
Ponieważ obrót jest wyśrodkowany wokół lewego górnego rogu kanwy, w przypadku większości kątów ustawionych w tym programie tekst jest obracany poza ekranem:
Bardzo często należy obrócić coś wyśrodkowanego wokół określonego RotateDegrees
punktu przestawnego przy użyciu tych wersji metod i RotateRadians
:
public void RotateDegrees (Single degrees, Single px, Single py)
public void RotateRadians (Single radians, Single px, Single py)
Na stronie Wyśrodkowana rotacja jest podobna do obrócenia podstawowego, z tą różnicą, że rozszerzona wersja RotateDegrees
elementu służy do ustawiania środka obrotu na ten sam punkt służący do umieszczania tekstu:
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);
}
Teraz tekst obraca się wokół punktu użytego do pozycjonowania tekstu, który jest wyśrodkowany w poziomie linii bazowej tekstu:
Podobnie jak w przypadku wyśrodkowanej wersji Scale
metody, wyśrodkowana wersja RotateDegrees
wywołania jest skrótem. Oto metoda:
RotateDegrees (degrees, px, py);
To wywołanie jest równoważne z następującymi elementami:
canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);
Dowiesz się, że czasami można łączyć Translate
wywołania z wywołaniami Rotate
. Oto na przykład RotateDegrees
wywołania i DrawText
na stronie wyśrodkowana rotacja ;
canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
Wywołanie RotateDegrees
jest równoważne dwóm Translate
wywołaniom i nieśrodkowanemu 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);
Wywołanie wyświetlania DrawText
tekstu w określonej lokalizacji jest równoważne Translate
wywołaniu tej lokalizacji, po DrawText
którym następuje w punkcie (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);
Dwa kolejne Translate
wywołania anulują się nawzajem:
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);
Koncepcyjnie dwa przekształcenia są stosowane w kolejności odwrotnej do sposobu ich wyświetlania w kodzie. Wywołanie DrawText
wyświetla tekst w lewym górnym rogu kanwy. Wywołanie RotateDegrees
obraca ten tekst względem lewego górnego rogu. Translate
Następnie wywołanie przenosi tekst do środka kanwy.
Zwykle istnieje kilka sposobów łączenia rotacji i tłumaczenia. Na stronie Obrócony tekst zostanie utworzony następujący ekran:
PaintSurface
Oto procedura obsługi RotatedTextPage
klasy:
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();
}
}
}
Wartości xCenter
i yCenter
wskazują środek kanwy. Wartość yText
jest nieco przesunięty z tego. Ta wartość jest współrzędną Y niezbędną do umieszczania tekstu tak, aby był naprawdę wyśrodkowany w pionie na stronie. Następnie pętla for
ustawia obrót na podstawie środka kanwy. Obrót jest zwiększany o 30 stopni. Tekst jest rysowany przy użyciu yText
wartości . Liczba pustych wartości przed określeniem wyrazu "ROTATE" w text
wartości została określona empirycznie, aby połączenie między tymi 12 ciągami tekstowymi wydawało się być dodecagonem.
Jednym ze sposobów uproszczenia tego kodu jest zwiększanie kąta obrotu o 30 stopni za każdym razem przez pętlę po wywołaniu DrawText
. Eliminuje to potrzebę wywołań do Save
i Restore
. Zwróć uwagę, że zmienna degrees
nie jest już używana w treści for
bloku:
for (int degrees = 0; degrees < 360; degrees += 30)
{
canvas.DrawText(text, xCenter, yText, textPaint);
canvas.RotateDegrees(30, xCenter, yCenter);
}
Można również użyć prostej formy RotateDegrees
, tworząc prefakcing pętli za pomocą wywołania , aby Translate
przenieść wszystko na środek kanwy:
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
Zmodyfikowane obliczenie nie zawiera yCenter
już elementu . Teraz wywołanie DrawText
wyśrodkuje tekst w pionie u góry kanwy.
Ponieważ przekształcenia są koncepcyjnie stosowane w przeciwieństwie do sposobu ich wyświetlania w kodzie, często można rozpocząć od bardziej globalnych przekształceń, a następnie bardziej lokalnych przekształceń. Jest to często najprostszy sposób łączenia rotacji i tłumaczenia.
Załóżmy na przykład, że chcesz narysować obiekt graficzny, który obraca się wokół jego środka, podobnie jak planeta obraca się na osi. Ale chcesz również, aby ten obiekt obracał się wokół środka ekranu podobnie jak planeta obraca się wokół słońca.
Można to zrobić, umieszczając obiekt w lewym górnym rogu kanwy, a następnie używając animacji, aby obrócić go w tym rogu. Następnie przetłumacz obiekt poziomo jak promień orbitalny. Teraz zastosuj drugą animowaną rotację, również wokół źródła. Dzięki temu obiekt obraca się po rogu. Teraz przetłumacz na środek kanwy.
PaintSurface
Oto procedura obsługi zawierająca te wywołania przekształcania w odwrotnej kolejności:
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);
}
}
Pola revolveDegrees
i rotateDegrees
są animowane. Ten program używa innej techniki animacji na Xamarin.FormsAnimation
podstawie klasy. (Ta klasa jest opisana w rozdziale 22 Bezpłatne pobieranie plików PDF z tematem Creating Mobile Apps with Xamarin.Forms) Zastąpienie OnAppearing
powoduje utworzenie dwóch Animation
obiektów z metodami wywołania zwrotnego, a następnie wywołanie Commit
ich przez czas trwania animacji:
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);
}
Pierwszy Animation
obiekt animuje revolveDegrees
od 0 stopni do 360 stopni w ciągu 10 sekund. Drugi animuje rotateDegrees
od 0 stopni do 360 stopni co 1 sekundę, a także unieważnia powierzchnię, aby wygenerować kolejne wywołanie PaintSurface
programu obsługi. Zastąpienie OnDisappearing
anuluje te dwie animacje:
protected override void OnDisappearing()
{
base.OnDisappearing();
this.AbortAnimation("revolveAnimation");
this.AbortAnimation("rotateAnimation");
}
Ugly Analog Clock program (tak zwany, ponieważ bardziej atrakcyjny zegar analogowy zostanie opisany w późniejszym artykule) używa obrotu, aby narysować minuty i godziny zegara i obracać ręce. Program rysuje zegar przy użyciu dowolnego układu współrzędnych na podstawie okręgu, który jest wyśrodkowany w punkcie (0, 0) z promieniem 100. Używa tłumaczenia i skalowania, aby rozwinąć i wyśrodkować ten okrąg na stronie.
Wywołania Translate
i Scale
mają zastosowanie globalnie do zegara, więc są to pierwsze wywołania, które mają być wywoływane po zainicjowaniu SKPaint
obiektów:
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));
...
}
}
Istnieje 60 znaków o dwóch różnych rozmiarach, które muszą być rysowane w okręgu przez całą dobę. Wywołanie DrawCircle
rysuje ten okrąg w punkcie (0, –90), który względem środka zegara odpowiada 12:00. Wywołanie RotateDegrees
zwiększa kąt obrotu o 6 stopni po każdym znaczniku znacznika. Zmienna angle
jest używana wyłącznie do określania, czy jest rysowany duży okrąg lub mały okrąg:
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
Na koniec program obsługi uzyskuje bieżący czas i oblicza stopnie rotacji godzin, minut i sekund. Każda ręka jest rysowana w pozycji 12:00, tak aby kąt obrotu był względem tego:
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();
}
}
Zegar jest z pewnością funkcjonalny, chociaż ręce są raczej surowe:
Aby uzyskać bardziej atrakcyjny zegar, zobacz artykuł SVG Path Data in SkiaSharp (Dane ścieżki SVG w usłudze SkiaSharp).