发现允许用于抚摸和填充路径的各种路径效果
路径效果
是类的
SKPathEffect
一个实例,该类使用类定义的八种静态创建方法之一创建。 然后,该
SKPathEffect
对象设置为
PathEffect
对象的属性
SKPaint
,以获取各种有趣的效果,例如,用小型复制路径抚摸一行:
路径效果允许你:
用点和短划线划线划线
用任何填充路径划线
用阴影线填充区域
使用平铺路径填充区域
使尖角圆角
向线条和曲线添加随机“抖动”
此外,还可以组合两个或多个路径效果。
本文还演示如何通过
GetFillPath
应用
SKPaint
属性(包括
StrokeWidth
和
PathEffect
)将一个路径转换为另一个路径的方法
SKPaint
。 这会导致一些有趣的技术,例如获取另一个路径的轮廓的路径。
GetFillPath
还有助于与路径效果有关。
点和短划线
“点和短划线
”一文介绍了该方法
PathEffect.CreateDash
的使用。 方法的第一个参数是包含两个或多个值的偶数数组,在短划线之间的短划线长度和短划线之间的间隔长度之间交替:
public static SKPathEffect CreateDash (Single[] intervals, Single phase)
这些值与笔划宽度 不 相关。 例如,如果笔划宽度为 10,并且需要由正方形短划线和正方形空白组成的线条,请将 intervals
数组设置为 { 10, 10 }。 该 phase
参数指示行开始的短划线模式中的位置。 在此示例中,如果希望线条以正方形间距开头,则设置为 phase
10。
短划线的末尾受 StrokeCap
属性 SKPaint
的影响。 对于宽笔划宽度,将此属性设置为 SKStrokeCap.Round
舍入短划线的末尾是很常见的。 在这种情况下,数组中的 intervals
值 不包括 舍入产生的额外长度。 这一事实意味着圆点需要指定零的宽度。 对于 10 的笔划宽度,若要创建一条圆点和相同直径点之间的空白线,请使用 intervals
{0, 20 } 的数组。
“动画虚线文本”页类似于文章“集成文本和图形”中所述的“大纲文本”页面,因为它通过将对象的属性SKPaint
设置为 Style
SKPaintStyle.Stroke
显示带轮廓的文本字符。 此外,动画虚线文本用于SKPathEffect.CreateDash
提供此轮廓的虚线外观,并且程序还对方法的参数SKPathEffect.CreateDash
进行动画处理phase
,使点似乎在文本字符周围旅行。 下面是横向模式下的页面:
该 AnimatedDottedTextPage
类首先定义一些常量,并替代 OnAppearing
动画的和 OnDisappearing
方法:
public class AnimatedDottedTextPage : ContentPage
const string text = "DOTTED";
const float strokeWidth = 10;
static readonly float[] dashArray = { 0, 2 * strokeWidth };
SKCanvasView canvasView;
bool pageIsActive;
public AnimatedDottedTextPage()
Title = "Animated Dotted Text";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
protected override void OnAppearing()
base.OnAppearing();
pageIsActive = true;
Device.StartTimer(TimeSpan.FromSeconds(1f / 60), () =>
canvasView.InvalidateSurface();
return pageIsActive;
protected override void OnDisappearing()
base.OnDisappearing();
pageIsActive = false;
处理程序 PaintSurface
首先创建一个 SKPaint
对象以显示文本。 根据 TextSize
屏幕宽度调整属性:
public class AnimatedDottedTextPage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create an SKPaint object to display the text
using (SKPaint textPaint = new SKPaint
Style = SKPaintStyle.Stroke,
StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue,
// Adjust TextSize property so text is 95% of screen width
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 0.95f * info.Width / textWidth;
// Find the text bounds
SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
// Calculate offsets to center the text on the screen
float xText = info.Width / 2 - textBounds.MidX;
float yText = info.Height / 2 - textBounds.MidY;
// Animate the phase; t is 0 to 1 every second
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 1 / 1);
float phase = -t * 2 * strokeWidth;
// Create dotted line effect based on dash array and phase
using (SKPathEffect dashEffect = SKPathEffect.CreateDash(dashArray, phase))
// Set it to the paint object
textPaint.PathEffect = dashEffect;
// And draw the text
canvas.DrawText(text, xText, yText, textPaint);
在方法的末尾, SKPathEffect.CreateDash
该方法使用 dashArray
定义为字段和动画 phase
值的方法调用。 实例 SKPathEffect
设置为 PathEffect
对象的属性 SKPaint
以显示文本。
或者,可以在测量文本并在页面上居中之前将SKPaint
对象设置为SKPathEffect
对象。 但是,在这种情况下,动画点和短划线会导致呈现的文本大小有一些变化,并且文本倾向于振动一点。 (尝试!)
你还将注意到,当动画点围绕文本字符绕行时,每个封闭曲线中都有一定点,这些点似乎弹出并不存在。 这是定义字符大纲开始和结束的路径。 如果路径长度不是短划线模式长度的整数倍数 (在本例中为 20 像素) ,则只有该模式的一部分可以容纳在路径末尾。
可以调整短划线模式的长度以适应路径的长度,但这需要确定路径的长度,这是路径 信息和枚举一文中介绍的技术。
点/短划线平滑程序对短划线图案本身进行动画处理,使短划线似乎划分为点,这合并为再次形成短划线:
类 DotDashMorphPage
会 OnAppearing
像上一个程序那样替代和 OnDisappearing
方法,但类将对象定义为 SKPaint
字段:
public class DotDashMorphPage : ContentPage
const float strokeWidth = 30;
static readonly float[] dashArray = new float[4];
SKCanvasView canvasView;
bool pageIsActive = false;
SKPaint ellipsePaint = new SKPaint
Style = SKPaintStyle.Stroke,
StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create elliptical path
using (SKPath ellipsePath = new SKPath())
ellipsePath.AddOval(new SKRect(50, 50, info.Width - 50, info.Height - 50));
// Create animated path effect
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 3 / 3);
float phase = 0;
if (t < 0.25f) // 1, 0, 1, 2 --> 0, 2, 0, 2
float tsub = 4 * t;
dashArray[0] = strokeWidth * (1 - tsub);
dashArray[1] = strokeWidth * 2 * tsub;
dashArray[2] = strokeWidth * (1 - tsub);
dashArray[3] = strokeWidth * 2;
else if (t < 0.5f) // 0, 2, 0, 2 --> 1, 2, 1, 0
float tsub = 4 * (t - 0.25f);
dashArray[0] = strokeWidth * tsub;
dashArray[1] = strokeWidth * 2;
dashArray[2] = strokeWidth * tsub;
dashArray[3] = strokeWidth * 2 * (1 - tsub);
phase = strokeWidth * tsub;
else if (t < 0.75f) // 1, 2, 1, 0 --> 0, 2, 0, 2
float tsub = 4 * (t - 0.5f);
dashArray[0] = strokeWidth * (1 - tsub);
dashArray[1] = strokeWidth * 2;
dashArray[2] = strokeWidth * (1 - tsub);
dashArray[3] = strokeWidth * 2 * tsub;
phase = strokeWidth * (1 - tsub);
else // 0, 2, 0, 2 --> 1, 0, 1, 2
float tsub = 4 * (t - 0.75f);
dashArray[0] = strokeWidth * tsub;
dashArray[1] = strokeWidth * 2 * (1 - tsub);
dashArray[2] = strokeWidth * tsub;
dashArray[3] = strokeWidth * 2;
using (SKPathEffect pathEffect = SKPathEffect.CreateDash(dashArray, phase))
ellipsePaint.PathEffect = pathEffect;
canvas.DrawPath(ellipsePath, ellipsePaint);
处理程序 PaintSurface
基于页面的大小创建椭圆路径,并执行设置 dashArray
和 phase
变量的长段代码。 由于动画变量 t
的范围从 0 到 1,这些块将 if
时间分解为四个季度,在这些季度中的每个季度, tsub
也范围为 0 到 1。 最后,程序将创建 SKPathEffect
并将其设置为 SKPaint
要绘制的对象。
从路径到路径
基于GetFillPath
对象中的SKPaint
设置将一个路径转换为另一个路径的方法SKPaint
。 若要查看工作原理,请将上一程序中的 canvas.DrawPath
调用替换为以下代码:
SKPath newPath = new SKPath();
bool fill = ellipsePaint.GetFillPath(ellipsePath, newPath);
SKPaint newPaint = new SKPaint
Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
canvas.DrawPath(newPath, newPaint);
在此新代码中, GetFillPath
调用将 ellipsePath
(转换为椭圆) newPath
,然后用 newPaint
椭圆显示。 对象 newPaint
是使用所有默认属性设置创建的,但该 Style
属性基于来自 GetFillPath
的布尔返回值设置。
视觉对象与颜色相同,但未newPaint
设置ellipsePaint
颜色。 与在中 ellipsePath
定义的简单省略号相比, newPath
包含定义一系列点和短划线的众多路径轮廓。 这是将 (的各种属性ellipsePaint
(具体、StrokeWidth
和 StrokeCap
PathEffect
) )ellipsePath
应用于和放置结果路径newPath
的结果。 该方法 GetFillPath
返回一个布尔值,该值指示是否要填充目标路径;在此示例中,返回值用于 true
填充路径。
尝试将设置更改为Style
其中newPaint
SKPaintStyle.Stroke
,你将看到以一像素宽度线条轮廓轮廓显示的各个路径轮廓。
使用路径进行抚摸
该方法 SKPathEffect.Create1DPath
在概念上类似于 SKPathEffect.CreateDash
指定路径,而不是短划线和间隙模式。 此路径复制多次以绘制线条或曲线。
public static SKPathEffect Create1DPath (SKPath path, Single advance,
Single phase, SKPath1DPathEffectStyle style)
一般情况下,要传递 Create1DPath
的路径会很小,并围绕点 (0,0) 。 该 advance
参数指示路径中心之间的距离,因为路径在行上复制。 通常将此参数设置为路径的近似宽度。 该 phase
参数在这里的作用与方法中 CreateDash
的作用相同。
有 SKPath1DPathEffectStyle
三个成员:
Translate
Rotate
Morph
该 Translate
成员使路径保持与沿线条或曲线复制的方向相同。 对于 Rotate
,路径根据曲线的正切值旋转。 路径具有其水平线的正常方向。 Morph
与 Rotate
路径本身也曲线相似,以匹配要绘制的线条的曲率。
“1D 路径效果”页演示了这三个选项。 OneDimensionalPathEffectPage.xaml 文件定义一个选取器,其中包含与枚举的三个成员对应的三个项:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Curves.OneDimensionalPathEffectPage"
Title="1D Path Effect">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Picker x:Name="effectStylePicker"
Title="Effect Style"
Grid.Row="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Translate</x:String>
<x:String>Rotate</x:String>
<x:String>Morph</x:String>
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
</Picker.SelectedIndex>
</Picker>
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface"
Grid.Row="1" />
</Grid>
</ContentPage>
OneDimensionalPathEffectPage.xaml.cs 代码隐藏文件将三个SKPathEffect
对象定义为字段。 所有这些都是使用使用SKPathEffect.Create1DPath
SKPath
创建SKPath.ParseSvgPathData
的对象创建的。 第一个是一个简单的框,第二个是菱形,第三个是矩形。 这些用于演示三种效果样式:
public partial class OneDimensionalPathEffectPage : ContentPage
SKPathEffect translatePathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 -10 L 10 -10, 10 10, -10 10 Z"),
24, 0, SKPath1DPathEffectStyle.Translate);
SKPathEffect rotatePathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 0 L 0 -10, 10 0, 0 10 Z"),
20, 0, SKPath1DPathEffectStyle.Rotate);
SKPathEffect morphPathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -25 -10 L 25 -10, 25 10, -25 10 Z"),
55, 0, SKPath1DPathEffectStyle.Morph);
SKPaint pathPaint = new SKPaint
Color = SKColors.Blue
public OneDimensionalPathEffectPage()
InitializeComponent();
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
if (canvasView != null)
canvasView.InvalidateSurface();
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath path = new SKPath())
path.MoveTo(new SKPoint(0, 0));
path.CubicTo(new SKPoint(2 * info.Width, info.Height),
new SKPoint(-info.Width, info.Height),
new SKPoint(info.Width, 0));
switch ((string)effectStylePicker.SelectedItem))
case "Translate":
pathPaint.PathEffect = translatePathEffect;
break;
case "Rotate":
pathPaint.PathEffect = rotatePathEffect;
break;
case "Morph":
pathPaint.PathEffect = morphPathEffect;
break;
canvas.DrawPath(path, pathPaint);
该 PaintSurface
处理程序创建一个循环于自身的 Bézier 曲线,并访问选取器以确定应使用哪个 PathEffect
曲线来绘制它。 这三个选项(Translate
以及 Rotate
Morph
)从左到右显示:
方法中指定的 SKPathEffect.Create1DPath
路径始终填充。 如果对象的属性PathEffect
设置为 1D 路径效果,则SKPaint
始终在方法中指定的DrawPath
路径进行笔划。 请注意,该 pathPaint
对象没有 Style
通常默认为 Fill
的设置,但无论路径如何。
示例中使用的 Translate
框为 20 像素方块,参数 advance
设置为 24。 当线条大致为水平或垂直时,此差异会导致框之间的间距,但当线条对角线为对角线时,框重叠一点,因为框的对角线为 28.3 像素。
示例中的菱形 Rotate
也宽 20 像素。 这 advance
一点设置为 20,以便随着线条的曲率旋转,钻石的点继续触摸。
示例中的矩形形状 Morph
宽 advance
为 50 像素,设置为 55,在矩形之间弯曲时,它们围绕 Bézier 曲线弯曲。
advance
如果参数小于路径的大小,则复制的路径可以重叠。 这可能会导致一些有趣的效果。 “链接链”页显示一系列重叠的圆圈,这些圆似乎类似于链接链,它挂起在猫的鲜明形状中:
看得很近,你会看到这些实际上不是圆圈。 链中的每个链接都是两个弧线,大小和定位,以便它们似乎与相邻链接连接。
一条统一重量分布的链或电缆以一根猫的形式挂起。 一个拱门,以倒射线的形式构建,受益于拱门重量的压力相等分布。 猫头形图有一个看似简单的数学描述:
y = a · cosh(x / a)
cosh 是双曲余弦函数。 对于 x 等于 0, cosh 为零, y 等于 a。 这就是禁区的中心。 与 余弦 函数一样, cosh 据说 是偶数,这意味着 cosh (-x) 等于 cosh (x) ,并且增加正或负参数的值增加。 这些值描述构成十一号边的曲线。
查找 适合手机 页面尺寸的相应值不是直接计算。 如果 w 和 h 是矩形的宽度和高度, 则满足以下 公式的最佳值:
cosh(w / 2 / a) = 1 + h / a
类中的LinkedChainPage
以下方法通过引用等号left
right
左侧和右侧的两个表达式来合并相等性。 对于 较小的值, left
right
大于 ;对于 a 的较大值, left
小于 right
。 该while
循环在以下值的最佳值上缩小:
float FindOptimumA(float width, float height)
Func<float, float> left = (float a) => (float)Math.Cosh(width / 2 / a);
Func<float, float> right = (float a) => 1 + height / a;
float gtA = 1; // starting value for left > right
float ltA = 10000; // starting value for left < right
while (Math.Abs(gtA - ltA) > 0.1f)
float avgA = (gtA + ltA) / 2;
if (left(avgA) < right(avgA))
ltA = avgA;
gtA = avgA;
return (gtA + ltA) / 2;
链接 SKPath
的对象在类的构造函数中创建,然后生成的 SKPathEffect
对象设置为 PathEffect
存储为字段的对象的属性 SKPaint
:
public class LinkedChainPage : ContentPage
const float linkRadius = 30;
const float linkThickness = 5;
Func<float, float, float> catenary = (float a, float x) => (float)(a * Math.Cosh(x / a));
SKPaint linksPaint = new SKPaint
Color = SKColors.Silver
public LinkedChainPage()
Title = "Linked Chain";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Create the path for the individual links
SKRect outer = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
SKRect inner = outer;
inner.Inflate(-linkThickness, -linkThickness);
using (SKPath linkPath = new SKPath())
linkPath.AddArc(outer, 55, 160);
linkPath.ArcTo(inner, 215, -160, false);
linkPath.Close();
linkPath.AddArc(outer, 235, 160);
linkPath.ArcTo(inner, 395, -160, false);
linkPath.Close();
// Set that path as the 1D path effect for linksPaint
linksPaint.PathEffect =
SKPathEffect.Create1DPath(linkPath, 1.3f * linkRadius, 0,
SKPath1DPathEffectStyle.Rotate);
处理程序的主要作业 PaintSurface
是创建一个通道本身的路径。 确定最佳 变量 并将其存储在 optA
变量中后,还需要计算窗口顶部的偏移量。 然后,它可以累积一组 SKPoint
用于猫的值,将其转换为路径,并使用之前创建 SKPaint
的对象绘制路径:
public class LinkedChainPage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Black);
// Width and height of catenary
int width = info.Width;
float height = info.Height - linkRadius;
// Find the optimum 'a' for this width and height
float optA = FindOptimumA(width, height);
// Calculate the vertical offset for that value of 'a'
float yOffset = catenary(optA, -width / 2);
// Create a path for the catenary
SKPoint[] points = new SKPoint[width];
for (int x = 0; x < width; x++)
points[x] = new SKPoint(x, yOffset - catenary(optA, x - width / 2));
using (SKPath path = new SKPath())
path.AddPoly(points, false);
// And render that path with the linksPaint object
canvas.DrawPath(path, linksPaint);
此程序定义用于 Create1DPath
在中心具有其 (0,0) 点的路径。 这似乎是合理的,因为路径的 (0,0) 点与它装饰的线条或曲线对齐。 但是,对于某些特殊效果,可以使用非居中 (0,0) 点。
“传送带”页创建一条类似于长长的传送带的路径,其顶部和底部弯曲,大小为窗口尺寸。 该路径用一 SKPaint
个简单的对象 20 像素宽和彩色灰色进行笔划,然后用另 SKPaint
一个 SKPathEffect
对象再次笔划,该对象引用了一个小存储桶的路径:
桶路径的 (0,0) 点是手柄,所以当 phase
参数是动画时,桶似乎围绕传送带旋转,也许在底部挖水,并将其倾倒在顶部。
该 ConveyorBeltPage
类使用重写 OnAppearing
和 OnDisappearing
方法实现动画。 存储桶的路径在页面的构造函数中定义:
public class ConveyorBeltPage : ContentPage
SKCanvasView canvasView;
bool pageIsActive = false;
SKPaint conveyerPaint = new SKPaint
Style = SKPaintStyle.Stroke,
StrokeWidth = 20,
Color = SKColors.DarkGray
SKPath bucketPath = new SKPath();
SKPaint bucketsPaint = new SKPaint
Color = SKColors.BurlyWood,
public ConveyorBeltPage()
Title = "Conveyor Belt";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Create the path for the bucket starting with the handle
bucketPath.AddRect(new SKRect(-5, -3, 25, 3));
// Sides
bucketPath.AddRoundedRect(new SKRect(25, -19, 27, 18), 10, 10,
SKPathDirection.CounterClockwise);
bucketPath.AddRoundedRect(new SKRect(63, -19, 65, 18), 10, 10,
SKPathDirection.CounterClockwise);
// Five slats
for (int i = 0; i < 5; i++)
bucketPath.MoveTo(25, -19 + 8 * i);
bucketPath.LineTo(25, -13 + 8 * i);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.CounterClockwise, 65, -13 + 8 * i);
bucketPath.LineTo(65, -19 + 8 * i);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.Clockwise, 25, -19 + 8 * i);
bucketPath.Close();
// Arc to suggest the hidden side
bucketPath.MoveTo(25, -17);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.Clockwise, 65, -17);
bucketPath.LineTo(65, -19);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.CounterClockwise, 25, -19);
bucketPath.Close();
// Make it a little bigger and correct the orientation
bucketPath.Transform(SKMatrix.MakeScale(-2, 2));
bucketPath.Transform(SKMatrix.MakeRotationDegrees(90));
存储桶创建代码使用两个转换完成,使存储桶变大一点,并侧转。 应用这些转换比调整上一代码中的所有坐标更容易。
处理程序 PaintSurface
首先定义传送带本身的路径。 这只是一对线条和一对半圆,用 20 像素宽深灰色线绘制:
public class ConveyorBeltPage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float width = info.Width / 3;
float verticalMargin = width / 2 + 150;
using (SKPath conveyerPath = new SKPath())
// Straight verticals capped by semicircles on top and bottom
conveyerPath.MoveTo(width, verticalMargin);
conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
SKPathDirection.Clockwise, 2 * width, verticalMargin);
conveyerPath.LineTo(2 * width, info.Height - verticalMargin);
conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
SKPathDirection.Clockwise, width, info.Height - verticalMargin);
conveyerPath.Close();
// Draw the conveyor belt itself
canvas.DrawPath(conveyerPath, conveyerPaint);
// Calculate spacing based on length of conveyer path
float length = 2 * (info.Height - 2 * verticalMargin) +
2 * ((float)Math.PI * width / 2);
// Value will be somewhere around 200
float spacing = length / (float)Math.Round(length / 200);
// Now animate the phase; t is 0 to 1 every 2 seconds
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 2 / 2);
float phase = -t * spacing;
// Create the buckets PathEffect
using (SKPathEffect bucketsPathEffect =
SKPathEffect.Create1DPath(bucketPath, spacing, phase,
SKPath1DPathEffectStyle.Rotate))
// Set it to the Paint object and draw the path again
bucketsPaint.PathEffect = bucketsPathEffect;
canvas.DrawPath(conveyerPath, bucketsPaint);
绘制传送带的逻辑在横向模式下不起作用。
桶应在传送带上隔约 200 像素。 但是,传送带的长度可能不是 200 像素的倍数,这意味着,随着 phase
动画的 SKPathEffect.Create1DPath
自变量,桶将弹出和不存在。
因此,程序首先计算一个名为 length
传送带长度的值。 由于传送带由直线和半圆组成,因此这是一个简单的计算。 接下来,通过除 length
以 200 计算存储桶数。 这将舍入到最接近的整数,然后该数字被划分为 length
。 结果是一个整数存储桶的间距。 该 phase
参数只是其中的一小部分。
再次从路径到路径
在传送带中的处理程序底部DrawSurface
,注释掉canvas.DrawPath
调用并将其替换为以下代码:
SKPath newPath = new SKPath();
bool fill = bucketsPaint.GetFillPath(conveyerPath, newPath);
SKPaint newPaint = new SKPaint
Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
canvas.DrawPath(newPath, newPaint);
与前面的示例 GetFillPath
一样,你将看到结果与颜色不同。 执行 GetFillPath
后,该 newPath
对象包含存储桶路径的多个副本,每个副本都位于动画在调用时定位它们的同一位置。
该方法 SKPathEffect.Create2DLines
用平行线填充区域,通常称为 阴影线。 该方法具有以下语法:
public static SKPathEffect Create2DLine (Single width, SKMatrix matrix)
该 width
参数指定阴影线的笔划宽度。 参数 matrix
是缩放和可选旋转的组合。 缩放因子指示 Skia 用于为阴影线设置空间的像素增量。 线之间的分隔是比例因子减去 width
参数。 如果缩放因子小于或等于 width
该值,则阴影线之间不会有空格,并且区域将显示填充。 为水平缩放和垂直缩放指定相同的值。
默认情况下,阴影线是水平线。 matrix
如果参数包含旋转,则顺时针旋转阴影线。
“阴影填充”页演示此路径效果。 类 HatchFillPage
将三个路径效果定义为字段,第一个用于水平阴影线,宽度为 3 像素,缩放因子指示它们相距 6 像素。 因此,线条之间的分隔线为三个像素。 第二个路径效果适用于宽度为 6 像素的垂直阴影线,宽度为 24 像素, (,因此分离为 18 像素) ,第三个是用于对角线 12 像素宽 36 像素。
public class HatchFillPage : ContentPage
SKPaint fillPaint = new SKPaint();
SKPathEffect horzLinesPath = SKPathEffect.Create2DLine(3, SKMatrix.MakeScale(6, 6));
SKPathEffect vertLinesPath = SKPathEffect.Create2DLine(6,
Multiply(SKMatrix.MakeRotationDegrees(90), SKMatrix.MakeScale(24, 24)));
SKPathEffect diagLinesPath = SKPathEffect.Create2DLine(12,
Multiply(SKMatrix.MakeScale(36, 36), SKMatrix.MakeRotationDegrees(45)));
SKPaint strokePaint = new SKPaint
Style = SKPaintStyle.Stroke,
StrokeWidth = 3,
Color = SKColors.Black
static SKMatrix Multiply(SKMatrix first, SKMatrix second)
SKMatrix target = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref target, first, second);
return target;
请注意矩阵 Multiply
方法。 由于水平和垂直缩放系数相同,因此缩放和旋转矩阵的乘数顺序并不重要。
处理程序 PaintSurface
将这三种路径效果与三种不同颜色结合使用 fillPaint
,以填充调整为适合页面的圆角矩形。 忽略Style
设置fillPaint
的属性;当对象包含从SKPathEffect.Create2DLine
中创建的路径效果时SKPaint
,不考虑填充区域:
public class HatchFillPage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath roundRectPath = new SKPath())
// Create a path
roundRectPath.AddRoundedRect(
new SKRect(50, 50, info.Width - 50, info.Height - 50), 100, 100);
// Horizontal hatch marks
fillPaint.PathEffect = horzLinesPath;
fillPaint.Color = SKColors.Red;
canvas.DrawPath(roundRectPath, fillPaint);
// Vertical hatch marks
fillPaint.PathEffect = vertLinesPath;
fillPaint.Color = SKColors.Blue;
canvas.DrawPath(roundRectPath, fillPaint);
// Diagonal hatch marks -- use clipping
fillPaint.PathEffect = diagLinesPath;
fillPaint.Color = SKColors.Green;
canvas.Save();
canvas.ClipPath(roundRectPath);
canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), fillPaint);
canvas.Restore();
// Outline the path
canvas.DrawPath(roundRectPath, strokePaint);
如果仔细查看结果,你将看到红色和蓝色阴影线不会精确限制在圆角矩形。 (这显然是基础 Skia code.) 如果这是不令人满意的,则为绿色对角线显示替代方法:圆角矩形用作剪裁路径,整个页面上绘制阴影线。
处理程序 PaintSurface
以简单地绘制圆角矩形的调用结束,因此可以看到红色和蓝色阴影线的差异:
Android 屏幕看起来并不像这样:屏幕截图的缩放导致细红线和细空间合并为看似宽阔的红线和更宽的空间。
通过此选项 SKPathEffect.Create2DPath
,可以使用水平和垂直复制的路径填充区域,实际上将区域平铺:
public static SKPathEffect Create2DPath (SKMatrix matrix, SKPath path)
SKMatrix
缩放系数指示复制路径的水平和垂直间距。 但不能使用此参数 matrix
旋转路径;如果想要旋转路径,请使用 Transform
由 SKPath
定义的方法旋转路径本身。
复制的路径通常与屏幕的左边缘和上边缘对齐,而不是填充的区域。 可以通过提供介于 0 和缩放因子之间的转换因子来指定从左侧和上侧的水平偏移量和垂直偏移量来替代此行为。
“路径磁贴填充”页演示了此路径效果。 用于平铺区域的路径定义为类中的 PathTileFillPage
字段。 水平坐标和垂直坐标范围为 –40 到 40,这意味着此路径为 80 像素正方形:
public class PathTileFillPage : ContentPage
SKPath tilePath = SKPath.ParseSvgPathData(
"M -20 -20 L 2 -20, 2 -40, 18 -40, 18 -20, 40 -20, " +
"40 -12, 20 -12, 20 12, 40 12, 40 40, 22 40, 22 20, " +
"-2 20, -2 40, -20 40, -20 8, -40 8, -40 -8, -20 -8 Z");
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
paint.Color = SKColors.Red;
using (SKPathEffect pathEffect =
SKPathEffect.Create2DPath(SKMatrix.MakeScale(64, 64), tilePath))
paint.PathEffect = pathEffect;
canvas.DrawRoundRect(
new SKRect(50, 50, info.Width - 50, info.Height - 50),
100, 100, paint);
在处理程序中PaintSurface
SKPathEffect.Create2DPath
,调用将水平间距和垂直间距设置为 64,导致 80 像素方形磁贴重叠。 幸运的是,路径类似于一个谜题块,与相邻磁贴很好地网格化:
原始屏幕截图中的缩放会导致某些扭曲,尤其是在 Android 屏幕上。
请注意,这些磁贴始终显示为整个,并且永远不会截断。 在前两个屏幕截图中,甚至还不清楚正在填充的区域是一个圆角矩形。 如果要将这些磁贴截断到特定区域,请使用剪辑路径。
尝试将Style
对象的属性设置为Stroke
该对象SKPaint
,你将看到轮廓而不是填充的各个磁贴。
还可以使用平铺位图填充区域,如 SkiaSharp 位图平铺一文所示。
在“三种方式”中介绍的圆角程序使用正切弧来弯曲七面图的点。 “另一轮”Heptagon 页显示了一种更简单的方法,该方法使用从SKPathEffect.CreateCorner
方法创建的路径效果:
public static SKPathEffect CreateCorner (Single radius)
尽管命名了单个参数 radius
,但必须将它设置为所需角半径的一半。 (这是基础 Skia code.) 的一个特征
PaintSurface
下面是类中的AnotherRoundedHeptagonPage
处理程序:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
int numVertices = 7;
float radius = 0.45f * Math.Min(info.Width, info.Height);
SKPoint[] vertices = new SKPoint[numVertices];
double vertexAngle = -0.5f * Math.PI; // straight up
// Coordinates of the vertices of the polygon
for (int vertex = 0; vertex < numVertices; vertex++)
vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle),
radius * (float)Math.Sin(vertexAngle));
vertexAngle += 2 * Math.PI / numVertices;
float cornerRadius = 100;
// Create the path
using (SKPath path = new SKPath())
path.AddPoly(vertices, true);
// Render the path in the center of the screen
using (SKPaint paint = new SKPaint())
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = 10;
// Set argument to half the desired corner radius!
paint.PathEffect = SKPathEffect.CreateCorner(cornerRadius / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawPath(path, paint);
// Uncomment DrawCircle call to verify corner radius
float offset = cornerRadius / (float)Math.Sin(Math.PI * (numVertices - 2) / numVertices / 2);
paint.Color = SKColors.Green;
// canvas.DrawCircle(vertices[0].X, vertices[0].Y + offset, cornerRadius, paint);
可以将此效果与基于对象的属性SKPaint
进行抚摸或填充Style
。 此处正在运行:
你将看到,此圆角与前面的程序相同。 如果需要更令人信服的是,角半径确实为 100,而不是调用中指定的 SKPathEffect.CreateCorner
50,则可以取消注释程序中的最终语句,并看到一个 100 半径圆在角上叠加。
有时,计算机图形完美无瑕的直线并不完全是你想要的,并且需要一点随机性。 在这种情况下,需要尝试此方法 SKPathEffect.CreateDiscrete
:
public static SKPathEffect CreateDiscrete (Single segLength, Single deviation, UInt32 seedAssist)
可以将此路径效果用于抚摸或填充。 线条分为连接段(其近似长度) segLength
并按不同方向扩展。 原始行的偏差范围由指定 deviation
。
最后一个参数是用于生成用于效果的伪随机序列的种子。 抖动效果对不同的种子看起来略有不同。 该参数的默认值为零,这意味着每当运行程序时效果都相同。 如果希望每当重新绘制屏幕时使用不同的抖动,可以将种子设置为 Millisecond
值的属性 DataTime.Now
(例如) 。
“ 抖动试验 ”页允许你在绘制矩形时尝试不同的值:
程序非常简单。 JitterExperimentPage.xaml 文件实例化两Slider
个元素和一个SKCanvasView
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Curves.JitterExperimentPage"
Title="Jitter Experiment">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Slider">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Slider x:Name="segLengthSlider"
Grid.Row="0"
ValueChanged="sliderValueChanged" />
<Label Text="{Binding Source={x:Reference segLengthSlider},
Path=Value,
StringFormat='Segment Length = {0:F0}'}"
Grid.Row="1" />
<Slider x:Name="deviationSlider"
Grid.Row="2"
ValueChanged="sliderValueChanged" />
<Label Text="{Binding Source={x:Reference deviationSlider},
Path=Value,
StringFormat='Deviation = {0:F0}'}"
Grid.Row="3" />
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="4"
PaintSurface="OnCanvasViewPaintSurface" />
</Grid>
</ContentPage>
PaintSurface
每当值发生更改时,将调用 JitterExperimentPage.xaml.cs 代码隐藏文件中的Slider
处理程序。 它使用两Slider
个值调用SKPathEffect.CreateDiscrete
,并使用该值来绘制矩形:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float segLength = (float)segLengthSlider.Value;
float deviation = (float)deviationSlider.Value;
using (SKPaint paint = new SKPaint())
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = 5;
paint.Color = SKColors.Blue;
using (SKPathEffect pathEffect = SKPathEffect.CreateDiscrete(segLength, deviation))
paint.PathEffect = pathEffect;
SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100);
canvas.DrawRect(rect, paint);
还可以将此效果用于填充,在这种情况下,填充区域的轮廓受这些随机偏差的影响。 “抖动文本”页演示如何使用此路径效果显示文本。 类处理程序JitterTextPage
中的PaintSurface
大多数代码专用于调整大小和居中文本:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
string text = "FUZZY";
using (SKPaint textPaint = new SKPaint())
textPaint.Color = SKColors.Purple;
textPaint.PathEffect = SKPathEffect.CreateDiscrete(3f, 10f);
// Adjust TextSize property so text is 95% of screen width
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 0.95f * info.Width / textWidth;
// Find the text bounds
SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
// Calculate offsets to center the text on the screen
float xText = info.Width / 2 - textBounds.MidX;
float yText = info.Height / 2 - textBounds.MidY;
canvas.DrawText(text, xText, yText, textPaint);
此处以横向模式运行:
你已经看到了两个小示例GetFillPath
SKPaint
,其中存在两个版本:
public Boolean GetFillPath (SKPath src, SKPath dst, Single resScale = 1)
public Boolean GetFillPath (SKPath src, SKPath dst, SKRect cullRect, Single resScale = 1)
只需要前两个参数。 该方法访问参数引用 src
的路径,根据对象中的 SKPaint
笔划属性修改路径数据, (包括 PathEffect
属性) ,然后将结果 dst
写入路径。 该 resScale
参数允许减少精度以创建较小的目标路径,参数 cullRect
可以消除矩形外部的轮廓。
此方法的一个基本用法根本不涉及路径效果:如果 SKPaint
对象的属性 Style
设置为 SKPaintStyle.Stroke
,并且 没有 其 PathEffect
设置,则 GetFillPath
创建一个路径,该路径表示源路径的 轮廓 ,就像画图属性中划一样。
例如,如果 src
路径是一个半径为 500 的简单圆,并且 SKPaint
该对象指定 100 的笔划宽度,则 dst
路径将变为两个同心圆,一个圆半径为 450,另一个圆半径为 550。 调用此方法 GetFillPath
是因为填充此 dst
路径与填充 src
路径相同。 但是,还可以笔划 dst
路径以查看路径轮廓。
点击到大纲路径演示了这一点。 TapToOutlineThePathPage.xamlSKCanvasView
文件中实例化并TapGestureRecognizer
对其进行实例化。 TapToOutlineThePathPage.xaml.cs 代码隐藏文件将三SKPaint
个对象定义为字段,两个对象用于在笔划宽度为 100 和 20 之间,第三个用于填充:
public partial class TapToOutlineThePathPage : ContentPage
bool outlineThePath = false;
SKPaint redThickStroke = new SKPaint
Style = SKPaintStyle.Stroke,
Color = SKColors.Red,
StrokeWidth = 100
SKPaint redThinStroke = new SKPaint
Style = SKPaintStyle.Stroke,
Color = SKColors.Red,
StrokeWidth = 20
SKPaint blueFill = new SKPaint
Style = SKPaintStyle.Fill,
Color = SKColors.Blue
public TapToOutlineThePathPage()
InitializeComponent();
void OnCanvasViewTapped(object sender, EventArgs args)
outlineThePath ^= true;
(sender as SKCanvasView).InvalidateSurface();
如果未点击屏幕,处理程序 PaintSurface
将使用 blueFill
和 redThickStroke
绘制对象来呈现循环路径:
public partial class TapToOutlineThePathPage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath circlePath = new SKPath())
circlePath.AddCircle(info.Width / 2, info.Height / 2,
Math.Min(info.Width / 2, info.Height / 2) -
redThickStroke.StrokeWidth);
if (!outlineThePath)
canvas.DrawPath(circlePath, blueFill);
canvas.DrawPath(circlePath, redThickStroke);
using (SKPath outlinePath = new SKPath())
redThickStroke.GetFillPath(circlePath, outlinePath);
canvas.DrawPath(outlinePath, blueFill);
canvas.DrawPath(outlinePath, redThinStroke);
圆按预期填充和笔划:
点击屏幕时,outlineThePath
设置为true
该对象,处理程序会创建一个新的SKPath
对象,PaintSurface
并在调用GetFillPath
redThickStroke
绘图对象时使用该对象作为目标路径。 然后,该目标路径将填充和笔划 redThinStroke
,从而生成以下内容:
两个红色圆清楚地表明原始循环路径已转换为两个圆形轮廓。
此方法在开发用于 SKPathEffect.Create1DPath
该方法的路径时非常有用。 复制路径时,这些方法中指定的路径始终填充。 如果不希望填充整个路径,则必须仔细定义大纲。
例如,在 链接链 示例中,链接定义了一系列四个弧线,每个弧对基于两个弧度来轮廓要填充的路径的区域。 可以替换类中的 LinkedChainPage
代码以略有不同的方式执行此操作。
首先,需要重新定义 linkRadius
常量:
const float linkRadius = 27.5f;
const float linkThickness = 5;
linkPath
现在,它只是基于该单个半径的两个弧线,具有所需的起始角度和扫描角度:
using (SKPath linkPath = new SKPath())
SKRect rect = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
linkPath.AddArc(rect, 55, 160);
linkPath.AddArc(rect, 235, 160);
using (SKPaint strokePaint = new SKPaint())
strokePaint.Style = SKPaintStyle.Stroke;
strokePaint.StrokeWidth = linkThickness;
using (SKPath outlinePath = new SKPath())
strokePaint.GetFillPath(linkPath, outlinePath);
// Set that path as the 1D path effect for linksPaint
linksPaint.PathEffect =
SKPathEffect.Create1DPath(outlinePath, 1.3f * linkRadius, 0,
SKPath1DPathEffectStyle.Rotate);
然后,该 outlinePath
对象是大纲 linkPath
的接收方,其笔划时使用在中指定的 strokePaint
属性。
使用此方法的另一个示例是接下来的方法中使用的路径。
组合路径效果
最后的两种静态创建方法SKPathEffect
是SKPathEffect.CreateSum
:SKPathEffect.CreateCompose
public static SKPathEffect CreateSum (SKPathEffect first, SKPathEffect second)
public static SKPathEffect CreateCompose (SKPathEffect outer, SKPathEffect inner)
这两种方法都结合了两个路径效果来创建复合路径效果。 该方法 CreateSum
创建一个路径效果,该效果与分别应用的两个路径效果类似,同时 CreateCompose
() inner
应用一个路径效果,然后应用 outer
该效果。
你已经了解了如何GetFillPath
基于SKPaint
属性 ((包括PathEffect
) )将一个路径转换为另一个路径的方法SKPaint
,因此,对象如何使用或CreateCompose
方法中指定的CreateSum
两个路径效果执行该操作的方式太神秘SKPaint
。
一个明显的用途 CreateSum
是定义一个 SKPaint
对象,该对象用一个路径效果填充路径,并用另一个路径效果笔划路径。 这在 Frame 示例中的 Cats 示例中演示,该示例在框架中显示一组带扇角边缘的猫:
该 CatsInFramePage
类首先定义多个字段。 可以从 SVG 路径数据文章中的PathDataCatPage
类中识别第一个字段。 第二个路径基于框架的扇贝图案的线条和弧线:
public class CatsInFramePage : ContentPage
// From PathDataCatPage.cs
SKPath catPath = SKPath.ParseSvgPathData(
"M 160 140 L 150 50 220 103" + // Left ear
"M 320 140 L 330 50 260 103" + // Right ear
"M 215 230 L 40 200" + // Left whiskers
"M 215 240 L 40 240" +
"M 215 250 L 40 280" +
"M 265 230 L 440 200" + // Right whiskers
"M 265 240 L 440 240" +
"M 265 250 L 440 280" +
"M 240 100" + // Head
"A 100 100 0 0 1 240 300" +
"A 100 100 0 0 1 240 100 Z" +
"M 180 170" + // Left eye
"A 40 40 0 0 1 220 170" +
"A 40 40 0 0 1 180 170 Z" +
"M 300 170" + // Right eye
"A 40 40 0 0 1 260 170" +
"A 40 40 0 0 1 300 170 Z");
SKPaint catStroke = new SKPaint
Style = SKPaintStyle.Stroke,
StrokeWidth = 5
SKPath scallopPath =
SKPath.ParseSvgPathData("M 0 0 L 50 0 A 60 60 0 0 1 -50 0 Z");
SKPaint framePaint = new SKPaint
Color = SKColors.Black
catPath
如果SKPaint
对象Style
属性设置为 Stroke
.,则可在方法中使用SKPathEffect.Create2DPath
此方法。 但是,如果 catPath
直接使用此程序,则猫的整个头部将被填充,并且胡须甚至不可见。 (尝试!) 必须获取该路径的大纲并在方法中使用该 SKPathEffect.Create2DPath
大纲。
构造函数执行此作业。 它首先应用两个转换来 catPath
移动 (0,0) 指向中心,并缩小其大小。 GetFillPath
获取中 outlinedCatPath
轮廓的所有轮廓,并在调用中使用该 SKPathEffect.Create2DPath
对象。 值 SKMatrix
中的缩放因子略大于猫的水平和垂直大小,以在磁贴之间提供一点缓冲区,而翻译因子从经验上派生,以便全猫在框架左上角可见:
public class CatsInFramePage : ContentPage
public CatsInFramePage()
Title = "Cats in Frame";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Move (0, 0) point to center of cat path
catPath.Transform(SKMatrix.MakeTranslation(-240, -175));
// Now catPath is 400 by 250
// Scale it down to 160 by 100
catPath.Transform(SKMatrix.MakeScale(0.40f, 0.40f));
// Get the outlines of the contours of the cat path
SKPath outlinedCatPath = new SKPath();
catStroke.GetFillPath(catPath, outlinedCatPath);
// Create a 2D path effect from those outlines
SKPathEffect fillEffect = SKPathEffect.Create2DPath(
new SKMatrix { ScaleX = 170, ScaleY = 110,
TransX = 75, TransY = 80,
Persp2 = 1 },
outlinedCatPath);
// Create a 1D path effect from the scallop path
SKPathEffect strokeEffect =
SKPathEffect.Create1DPath(scallopPath, 75, 0, SKPath1DPathEffectStyle.Rotate);
// Set the sum the effects to frame paint
framePaint.PathEffect = SKPathEffect.CreateSum(fillEffect, strokeEffect);
然后,构造函数调用 SKPathEffect.Create1DPath
扇贝框架。 请注意,路径的宽度为 100 像素,但前进为 75 像素,以便复制的路径在框架周围重叠。 构造函数的最终语句调用 SKPathEffect.CreateSum
以合并这两个路径效果并将结果 SKPaint
设置为对象。
所有这些工作都允许 PaintSurface
处理程序非常简单。 它只需要定义一个矩形并使用 framePaint
:
public class CatsInFramePage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKRect rect = new SKRect(50, 50, info.Width - 50, info.Height - 50);
canvas.ClipRect(rect);
canvas.DrawRect(rect, framePaint);
路径效果背后的算法始终会导致显示用于绘制或填充的整个路径,这可能会导致某些视觉对象出现在矩形外部。 调用前DrawRect
的ClipRect
调用允许视觉对象更加简洁。 (尝试而不剪裁!)
常用于 SKPathEffect.CreateCompose
向另一个路径效果添加一些抖动。 当然可以自行试验,但下面是一个略有不同示例:
虚线填充一个省略号,其中包含虚线的阴影线。 类中的 DashedHatchLinesPage
大部分工作在字段定义中正确执行。 这些字段定义短划线效果和阴影效果。 它们被定义为static
因为它们随后在定义中的调用中SKPathEffect.CreateCompose
SKPaint
被引用:
public class DashedHatchLinesPage : ContentPage
static SKPathEffect dashEffect =
SKPathEffect.CreateDash(new float[] { 30, 30 }, 0);
static SKPathEffect hatchEffect = SKPathEffect.Create2DLine(20,
Multiply(SKMatrix.MakeScale(60, 60),
SKMatrix.MakeRotationDegrees(45)));
SKPaint paint = new SKPaint()
PathEffect = SKPathEffect.CreateCompose(dashEffect, hatchEffect),
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue
static SKMatrix Multiply(SKMatrix first, SKMatrix second)
SKMatrix target = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref target, first, second);
return target;
处理程序 PaintSurface
只需要包含标准开销,以及对以下项的一次调用 DrawOval
:
public class DashedHatchLinesPage : ContentPage
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawOval(info.Width / 2, info.Height / 2,
0.45f * info.Width, 0.45f * info.Height,
paint);
正如你已经发现的那样,阴影线并不精确限制为区域的内部,在此示例中,它们始终以整条短划线从左侧开始:
现在,你已经看到了从简单点和短划线到奇怪组合的路径效果,请使用你的想象力,看看你可以创建的内容。
SkiaSharp API
SkiaSharpFormsDemos (示例)