线和笔划大写字母
了解如何使用 SkiaSharp 绘制具有不同笔触端点的线条
在 SkiaSharp 中,呈现单个线条与呈现一系列连接的直线很不一样。 但是,即使在绘制单个线条时,也通常需要为线条提供特定的笔触宽度。 随着这些线条变宽,线条末尾的外观也变得很重要。 线条末尾的外观称为“笔触端点”:
绘制单个线条时,SKCanvas
会定义一个简单的 DrawLine
方法,其参数指示线条的起始坐标和结束坐标,以及 SKPaint
对象:
canvas.DrawLine (x0, y0, x1, y1, paint);
默认情况下, 新实例化的 SKPaint
对象的 StrokeWidth
属性为 0,在呈现一个一像素粗的线条时,其效果与值 1 相同。 这在手机等高分辨率设备上显得非常薄,因此你可能想要将 StrokeWidth
设置为更大的值。 但是,一旦你开始绘制相当粗的线条,这就会引发另一个问题:应该如何呈现这些粗线条的开头和结尾?
线条的开头和结尾的外观称为“线条端点”,在 Skia 中,它称为“笔触端点”。 此上下文中的“端点”一词指的是一种帽子,即位于线条末尾的东西。 将 SKPaint
对象的 StrokeCap
属性设置为 SKStrokeCap
枚举的下列成员之一:
Butt
(默认值)Square
Round
最好用示例程序阐明它们。 示例程序的“SkiaSharp 线条和路径”部分的开头是一个标题为“笔划端点”的页面,它基于 StrokeCapsPage
类。 此页定义了一个 PaintSurface
事件处理程序,它会遍历 SKStrokeCap
枚举的三个成员,同时显示枚举成员的名称,并使用该笔触端点绘制线条:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPaint textPaint = new SKPaint
{
Color = SKColors.Black,
TextSize = 75,
TextAlign = SKTextAlign.Center
};
SKPaint thickLinePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Orange,
StrokeWidth = 50
};
SKPaint thinLinePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Black,
StrokeWidth = 2
};
float xText = info.Width / 2;
float xLine1 = 100;
float xLine2 = info.Width - xLine1;
float y = textPaint.FontSpacing;
foreach (SKStrokeCap strokeCap in Enum.GetValues(typeof(SKStrokeCap)))
{
// Display text
canvas.DrawText(strokeCap.ToString(), xText, y, textPaint);
y += textPaint.FontSpacing;
// Display thick line
thickLinePaint.StrokeCap = strokeCap;
canvas.DrawLine(xLine1, y, xLine2, y, thickLinePaint);
// Display thin line
canvas.DrawLine(xLine1, y, xLine2, y, thinLinePaint);
y += 2 * textPaint.FontSpacing;
}
}
对于 SKStrokeCap
枚举的每个成员,该处理程序都会绘制两个线条,一个线条粗细为 50 像素,另一个线条位于顶部,笔触粗细为 2 像素。 第二个线条是为了阐明线条的几何起点和终点,与线条粗细和笔触端点无关:
如你所看到的,Square
和 Round
笔触端点在线条的开头和末尾有效地将线条的长度延长了笔触宽度的一半。 当需要确定呈现的图形对象的尺寸时,这样的延长就变得很重要。
SKCanvas
类还包含用于绘制多个线条的另一种方法,它有点奇特:
DrawPoints (SKPointMode mode, points, paint)
points
参数是一个 SKPoint
值的数组,mode
是 SKPointMode
枚举的成员,其中包含三个成员:
Points
,用于呈现各个点Lines
,用于连接每对点Polygon
,用于连接所有连续点
“多线条”页演示了此方法。 MultipleLinesPage.xaml 文件实例化了两个 Picker
视图,使你能够选择 SKPointMode
枚举的一个成员和 SKStrokeCap
枚举的一个成员:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Paths.MultipleLinesPage"
Title="Multiple Lines">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Picker x:Name="pointModePicker"
Title="Point Mode"
Grid.Row="0"
Grid.Column="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type skia:SKPointMode}">
<x:Static Member="skia:SKPointMode.Points" />
<x:Static Member="skia:SKPointMode.Lines" />
<x:Static Member="skia:SKPointMode.Polygon" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<Picker x:Name="strokeCapPicker"
Title="Stroke Cap"
Grid.Row="0"
Grid.Column="1"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type skia:SKStrokeCap}">
<x:Static Member="skia:SKStrokeCap.Butt" />
<x:Static Member="skia:SKStrokeCap.Round" />
<x:Static Member="skia:SKStrokeCap.Square" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<skiaforms:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2" />
</Grid>
</ContentPage>
请注意,SkiaSharp 命名空间声明稍有不同,因为需要 SkiaSharp
命名空间来引用 SKPointMode
和 SKStrokeCap
枚举的成员。 这两个 Picker
视图的 SelectedIndexChanged
处理程序只会使 SKCanvasView
对象失效:
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
if (canvasView != null)
{
canvasView.InvalidateSurface();
}
}
此处理程序需要检查 SKCanvasView
对象是否存在,因为在 XAML 文件中将 Picker
的 SelectedIndex
属性设置为 0 时,会首先调用事件处理程序,这发生在实例化 SKCanvasView
之前。
PaintSurface
处理程序会从 Picker
视图中获取两个枚举值:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create an array of points scattered through the page
SKPoint[] points = new SKPoint[10];
for (int i = 0; i < 2; i++)
{
float x = (0.1f + 0.8f * i) * info.Width;
for (int j = 0; j < 5; j++)
{
float y = (0.1f + 0.2f * j) * info.Height;
points[2 * j + i] = new SKPoint(x, y);
}
}
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.DarkOrchid,
StrokeWidth = 50,
StrokeCap = (SKStrokeCap)strokeCapPicker.SelectedItem
};
// Render the points by calling DrawPoints
SKPointMode pointMode = (SKPointMode)pointModePicker.SelectedItem;
canvas.DrawPoints(pointMode, points, paint);
}
屏幕截图显示了各种 Picker
选择:
左侧的 iPhone 显示 SKPointMode.Points
枚举成员如何使 DrawPoints
将 SKPoint
数组中的每个点呈现为正方形(如果线条端点为 Butt
或 Square
)。 如果线条端点为 Round
,则呈现圆圈。
Android 屏幕截图显示了 SKPointMode.Lines
的结果。 DrawPoints
方法使用指定的线条端点(在本例中为 Round
)在每对 SKPoint
值之间绘制一条线。
当你改用 SKPointMode.Polygon
时,会在数组中的连续点之间绘制一条线,但如果仔细观察,你会发现这些线条未连接。 每个单独的线条以指定的线条端点开头和结尾。 如果选择 Round
端点,则线条可能显示为已连接,但它们实际上未连接。
线条连接与否是处理图形路径的关键方面。