旋轉轉換
使用 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檔案會顯示一些文字,其基準會置中於頁面上,並根據範圍為 –360 到 360 的 來旋轉Slider
。 以下是處理程序的相關 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
。 值中 text
“ROTATE” 一詞之前的空白數目是實證決定,使這 12 個文字字串之間的連接似乎是一個 dodecagon。
簡化此程序代碼的其中一種方法是每次在呼叫後 DrawText
透過迴圈遞增 30 度旋轉角度。 這樣就不需要呼叫 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);
}
}
和 revolveDegrees
rotateDegrees
欄位會以動畫顯示。 此程式會根據 Xamarin.FormsAnimation
類別使用不同的動畫技術。 (本類別描述於 第 22 章使用 建立Mobile AppsXamarin.Forms 的免費 PDF 下載 。 覆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
物件會在 10 秒內從 0 度動畫到 revolveDegrees
360 度。 第二個動畫 rotateDegrees
從 0 度到每 1 秒 360 度,也會使表面失效,以產生處理程式的另一個呼叫 PaintSurface
。 覆寫會 OnDisappearing
取消這兩個動畫:
protected override void OnDisappearing()
{
base.OnDisappearing();
this.AbortAnimation("revolveAnimation");
this.AbortAnimation("rotateAnimation");
}
醜陋的類比時鐘程式(因此所謂的,因為稍後的文章將描述更有吸引力的類比時鐘)使用旋轉來繪製時鐘的分鐘和小時標記,並旋轉手。 程式會使用任意座標系統,根據以點 (0, 0, 0) 為半徑 100 的圓圈繪製時鐘。 它會使用翻譯和縮放來展開和置中頁面上的圓形。
Translate
和 Scale
呼叫會全域套用至時鐘,因此,這些呼叫是在物件初始化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();
}
}
時鐘當然是功能性的,雖然手相當粗暴:
如需更具吸引力的時鐘,請參閱 SkiaSharp 中的 SVG 路徑數據一文。