SkiaSharp 中的点和短划线
掌握在 SkiaSharp 中绘制点划线的复杂技巧
利用 SkiaSharp,可以绘制由点和短划线组成的非实线:
可以使用路径效果来完成此操作,该效果是 SKPathEffect
类的一个实例,将其设置为 SKPaint
的 PathEffect
属性。 可以使用 SKPathEffect
定义的静态创建方法之一创建路径效果(或组合路径效果)。 (SKPathEffect
是 SkiaSharp 支持的六种效果之一;其他效果在 SkiaSharp 效果部分进行了介绍。)
若要绘制点状或短划线式虚线,请使用 SKPathEffect.CreateDash
静态方法。 有两个参数:第一个是 float
值的数组,表示点和短划线的长度以及它们之间的空隙的长度。 此数组必须有偶数个元素,并且至少含两个元素。 (数组中可以有零个元素,但这会导致生成实线。)如果有两个元素,则第一个元素是点或短划线的长度,第二个元素是下一个点或短划线之前的间隙长度。 如果有两个以上的元素,则它们按以下顺序排列:短划线长度、间隙长度、短划线长度、间隙长度,依次类推。
通常,短划线和间隙长度需要为笔划宽度的倍数。 例如,如果笔划宽度为 10 像素,则数组 { 10, 10 } 将绘制一条点状虚线,其中点和间隙长度与笔划粗细的长度相同。
但是,SKPaint
对象的 StrokeCap
设置也会影响这些点和短划线。 你很快就会看到,这对该数组的元素有影响。
点和短划线页面上演示了点划线。 DotsAndDashesPage.xaml 文件实例化两个 Picker
视图,一个用于让你选择笔划末端,第二个用于选择短划线数组:
<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.DotsAndDashesPage"
Title="Dots and Dashes">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Picker x:Name="strokeCapPicker"
Title="Stroke Cap"
Grid.Row="0"
Grid.Column="0"
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>
<Picker x:Name="dashArrayPicker"
Title="Dash Array"
Grid.Row="0"
Grid.Column="1"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>10, 10</x:String>
<x:String>30, 10</x:String>
<x:String>10, 10, 30, 10</x:String>
<x:String>0, 20</x:String>
<x:String>20, 20</x:String>
<x:String>0, 20, 20, 20</x:String>
</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>
dashArrayPicker
中的前三项假设笔画宽度为 10 像素。 { 10, 10 } 数组表示点状虚线,{ 30, 10 } 表示短划线式虚线,{ 10, 10, 30, 10 } 表示点划线。 (其他三个将在稍后讨论。)
DotsAndDashesPage
代码隐藏文件包含 PaintSurface
事件处理程序和一对用于访问 Picker
视图的帮助程序例程:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Blue,
StrokeWidth = 10,
StrokeCap = (SKStrokeCap)strokeCapPicker.SelectedItem,
PathEffect = SKPathEffect.CreateDash(GetPickerArray(dashArrayPicker), 20)
};
SKPath path = new SKPath();
path.MoveTo(0.2f * info.Width, 0.2f * info.Height);
path.LineTo(0.8f * info.Width, 0.8f * info.Height);
path.LineTo(0.2f * info.Width, 0.8f * info.Height);
path.LineTo(0.8f * info.Width, 0.2f * info.Height);
canvas.DrawPath(path, paint);
}
float[] GetPickerArray(Picker picker)
{
if (picker.SelectedIndex == -1)
{
return new float[0];
}
string str = (string)picker.SelectedItem;
string[] strs = str.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
float[] array = new float[strs.Length];
for (int i = 0; i < strs.Length; i++)
{
array[i] = Convert.ToSingle(strs[i]);
}
return array;
}
在以下屏幕截图中,最左侧的 iOS 屏幕显示了一条点状虚线:
但是,使用数组 { 10, 10 } 时,Android 屏幕本来也应该显示点状虚线,但实际上这条线是实线。 发生了什么情况? 问题是 Android 屏幕也有一个 Square
笔画末端设置。 这会使所有短划线都延长笔画宽度的一半,导致空隙被填满。
当使用 Square
或 Round
笔划末端时,若要解决这个问题,必须将数组中的短划线长度减小到笔划长度(有时会导致短划线长度为 0),并将间隙长度增加到笔划长度。 下面是 XAML 文件 Picker
中最后三个短划线数组的计算方法:
- { 10, 10 } 变为 { 0, 20 } 得到点状虚线
- { 30, 10 } 变为 { 20, 20 } 得到短划线式虚线
- { 10, 10, 30, 10 } 变为 { 0, 20, 20, 20} 得到点划线
UWP 屏幕显示了笔画末端为 Round
时的点划线。 Round
笔画末端通常可在粗线条中提供最佳的点和短划线外观。
到目前为止,还没有提到 SKPathEffect.CreateDash
方法的第二个参数。 此参数名为 phase
,它指代线条开头的点划模式中的偏移量。 例如,如果短划线数组为 { 10, 10 } 且 phase
为 10,则线条以间隙而不是点开头。
phase
参数的一个有趣的应用是在动画中。 Animated Spiral 页面与 Archimedean Spiral 页面类似,除了 AnimatedSpiralPage
类使用 Xamarin.FormsDevice.Timer
方法使 phase
参数显示动画效果:
public class AnimatedSpiralPage : ContentPage
{
const double cycleTime = 250; // in milliseconds
SKCanvasView canvasView;
Stopwatch stopwatch = new Stopwatch();
bool pageIsActive;
float dashPhase;
public AnimatedSpiralPage()
{
Title = "Animated Spiral";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
protected override void OnAppearing()
{
base.OnAppearing();
pageIsActive = true;
stopwatch.Start();
Device.StartTimer(TimeSpan.FromMilliseconds(33), () =>
{
double t = stopwatch.Elapsed.TotalMilliseconds % cycleTime / cycleTime;
dashPhase = (float)(10 * t);
canvasView.InvalidateSurface();
if (!pageIsActive)
{
stopwatch.Stop();
}
return pageIsActive;
});
}
···
}
当然,你必须实际运行程序才能看到动画: