在 SkiaSharp 中用手指绘制

Download Sample下载示例

使用手指在画布上绘制。

可以不断更新和显示对象 SKPath。 此功能允许使用路径进行交互式绘制,例如在手指绘制程序中。

An exercise in finger painting

Xamarin.Forms中的触摸支持不允许在屏幕上跟踪单个手指,因此已开发Xamarin.Forms触摸跟踪效果来提供额外的触摸支持。 《从效果调用事件》这篇文章中介绍了此效果。 示例程序触摸跟踪效果演示包括两个使用 SkiaSharp 的页面,包括手指绘画程序。

SkiaSharpFormsDemos 解决方案包括此触摸跟踪事件。 .NET Standard 库项目包括 TouchEffect 类、TouchActionType 枚举、TouchActionEventHandler 委托和 TouchActionEventArgs 类。 每个平台项目都包含该平台的 TouchEffect 类;iOS 项目还包含一个 TouchRecognizer 类。

SkiaSharpFormsDemos 中的“手指绘画”页是手指绘画的简化实现。 它不允许选择颜色或笔划宽度,它无法清除画布,当然你也无法保存你的艺术品。

FingerPaintPage.xaml 文件将 SKCanvasView 放入一个单元格 Grid 中,并将 TouchEffect 附加到 Grid 中:

<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"
             xmlns:tt="clr-namespace:TouchTracking"
             x:Class="SkiaSharpFormsDemos.Paths.FingerPaintPage"
             Title="Finger Paint">

    <Grid BackgroundColor="White">
        <skia:SKCanvasView x:Name="canvasView"
                           PaintSurface="OnCanvasViewPaintSurface" />
        <Grid.Effects>
            <tt:TouchEffect Capture="True"
                            TouchAction="OnTouchEffectAction" />
        </Grid.Effects>
    </Grid>
</ContentPage>

TouchEffect 直接附加到 SKCanvasView 在部分平台上不起作用。

FingerPaintPage.xaml.cs 代码隐藏文件定义了两个用于存储 SKPath 对象的集合,以及用于呈现这些路径的 SKPaint 对象:

public partial class FingerPaintPage : ContentPage
{
    Dictionary<long, SKPath> inProgressPaths = new Dictionary<long, SKPath>();
    List<SKPath> completedPaths = new List<SKPath>();

    SKPaint paint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = 10,
        StrokeCap = SKStrokeCap.Round,
        StrokeJoin = SKStrokeJoin.Round
    };

    public FingerPaintPage()
    {
        InitializeComponent();
    }
    ...
}

顾名思义,inProgressPaths 字典存储当前由一个或多个手指绘制的路径。 该字典键是触摸事件附带的触摸 ID。 completedPaths 字段是正在绘制路径的手指从屏幕上离开时完成的路径的集合。

处理程序 TouchAction 管理这两个集合。 当手指第一次触摸屏幕时,会向 inProgressPaths 添加新的 SKPath。 当手指移动时,其他点将添加到路径中。 当手指松开时,相应路径将传输到集合 completedPaths。 你可以同时使用多个手指进行绘制。 每次更改一个路径或集合后,SKCanvasView 都会失效:

public partial class FingerPaintPage : ContentPage
{
    ...
    void OnTouchEffectAction(object sender, TouchActionEventArgs args)
    {
        switch (args.Type)
        {
            case TouchActionType.Pressed:
                if (!inProgressPaths.ContainsKey(args.Id))
                {
                    SKPath path = new SKPath();
                    path.MoveTo(ConvertToPixel(args.Location));
                    inProgressPaths.Add(args.Id, path);
                    canvasView.InvalidateSurface();
                }
                break;

            case TouchActionType.Moved:
                if (inProgressPaths.ContainsKey(args.Id))
                {
                    SKPath path = inProgressPaths[args.Id];
                    path.LineTo(ConvertToPixel(args.Location));
                    canvasView.InvalidateSurface();
                }
                break;

            case TouchActionType.Released:
                if (inProgressPaths.ContainsKey(args.Id))
                {
                    completedPaths.Add(inProgressPaths[args.Id]);
                    inProgressPaths.Remove(args.Id);
                    canvasView.InvalidateSurface();
                }
                break;

            case TouchActionType.Cancelled:
                if (inProgressPaths.ContainsKey(args.Id))
                {
                    inProgressPaths.Remove(args.Id);
                    canvasView.InvalidateSurface();
                }
                break;
        }
    }
    ...
    SKPoint ConvertToPixel(Point pt)
    {
        return new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
                           (float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
    }
}

触摸跟踪事件附带的点是 Xamarin.Forms 坐标;这些点必须转换为 SkiaSharp 坐标,也就是像素。 这是 ConvertToPixel 方法的目的。

然后,处理程序 PaintSurface 会同时呈现两个路径集合。 先前完成的路径显示在正在进行的路径下方:

public partial class FingerPaintPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKCanvas canvas = args.Surface.Canvas;
        canvas.Clear();

        foreach (SKPath path in completedPaths)
        {
            canvas.DrawPath(path, paint);
        }

        foreach (SKPath path in inProgressPaths.Values)
        {
            canvas.DrawPath(path, paint);
        }
    }
    ...
}

你的手指画只受你的才华限制:

Triple screenshot of the Finger Paint page

现在,你已了解如何使用参数公式绘制线条和定义曲线。 SkiaSharp 曲线和路径的后面部分介绍了 SKPath 支持的各种曲线类型。 但有用的先决条件是探索 SkiaSharp Transforms