SkiaSharp DrawPath点击识别:如何判断用户点击的具体路径?
实现SkiaSharp路径点击识别的解决方案
要解决SkiaSharp中无法区分点击路径的问题,核心思路是保存所有可点击路径的实例,在点击时通过坐标转换和路径包含测试来判断点击的区域,具体步骤如下:
1. 调整XAML绑定点击事件
给TapGestureRecognizer添加Tapped事件处理方法,用于响应点击操作:
<skia:SKCanvasView PaintSurface="OnCanvasViewPaintSurface" VerticalOptions="FillAndExpand" > <skia:SKCanvasView.GestureRecognizers> <TapGestureRecognizer x:Name="tapGestureRecognizer" Tapped="OnTapGestureRecognizerTapped" /> </skia:SKCanvasView.GestureRecognizers> </skia:SKCanvasView>
2. 持久化保存可点击路径
在页面类中添加字段,保存所有路径的实例、区域标识和当前颜色,避免绘制后丢弃路径:
// 存储路径、区域ID、当前显示颜色 private List<(SKPath Path, string RegionId, SKColor CurrentColor)> _clickablePaths = new();
3. 修改绘制逻辑,复用保存的路径
调整OnCanvasViewPaintSurface方法,首次绘制时初始化路径列表,后续重绘使用保存的颜色状态:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // 仅首次绘制时初始化路径 if (_clickablePaths.Count == 0) { // 轮胎区域(region 72) var tirePath = SKPath.ParseSvgPathData("M52 152.1c-8.8 1.5-19.4 7.1-25.9 13.7-24.4 25.1-14.7 66.8 18.3 78.2 28.1 9.6 58-8.3 62.6-37.4 4-25.5-12.2-48.7-37.8-54.2-7.5-1.5-9.3-1.6-17.2-.3zm19.7 19.8c19.8 9 23.8 34.3 7.7 49-12 11-29.9 10.2-40.8-1.9-11.1-12.3-10.7-30.2 1.1-41.1 6.5-6 11.4-7.9 20.3-7.9 5.2 0 8.8.6 11.7 1.9z"); _clickablePaths.Add((tirePath, "72", SKColors.Black)); // 轮圈区域(region 73) var wheelPath = SKPath.ParseSvgPathData("M55.2 172c-1.3.6-2.8.8-3.3.4-.5-.3-.9 0-.9.6s-.4.8-1 .5c-.5-.3-1-.1-1 .4 0 .6-.9 1.1-1.9 1.1-1 0-2.1.7-2.5 1.5-.3.8-1.2 1.5-1.9 1.5-.8 0-1.9 1.1-2.5 2.5-.6 1.4-1.6 2.5-2.1 2.5-1.3 0-4.3 6.8-5.3 11.8-1.1 5.9 2 19.2 4.5 19.2.5 0 .6.4.3.9-.7 1.1 7.2 8.1 9.1 8.1.7 0 1.3.4 1.3.8 0 .5.6.9 1.3 1 .6.1 3.5.6 6.2 1.2 5.5 1.2 13.5.5 13.5-1.2 0-.6.3-.9.6-.5.4.3 1.3-.1 2.1-.9s1.9-1.2 2.4-.9c.5.4.9.1.9-.6s.3-1 .6-.6c.9.8 8.5-7.4 7.8-8.5-.3-.4 0-.8.7-.8s1-.3.6-.6c-.3-.4 0-1.2.7-2 3.5-3.5 2.5-19.3-1.6-23.8-1-1-1.5-2.4-1.1-2.9.3-.6.1-.7-.5-.3-.7.4-1.2.1-1.2-.8s-.5-1.5-1.2-1.4c-.7.2-1.2-.3-1.3-1 0-.6-.4-1.3-1-1.4-.5-.1-1.8-.9-2.8-1.8-3.3-3.1-15.6-5.6-19.5-4z"); _clickablePaths.Add((wheelPath, "73", StringToColor("#E7FFFF"))); // 外围黑线区域(region 14) var linePath = SKPath.ParseSvgPathData("M111 112.6c-20.7 2.6-23.5 3.6-32.6 11.5l-5.5 4.9h-7.3c-9.1 0-9.8.7-9.4 9.2l.3 6.3 7.5.8c29.3 3.2 50.1 25.6 50 54-.1 26-19.5 48.8-45 52.8-4.1.6-8.1 1.3-8.8 1.5-1.5.4-1.8 30.4-.2 30.4.6 0 1-5.5 1-14.4v-14.3l6.1-.7c22.7-2.6 41.7-19.7 47.5-42.6 2.2-8.7 1.5-23.2-1.6-31.5-3-8.1-10.1-18.6-15.8-23.5-9.4-8.1-24.3-13.9-35.5-14h-3.8l.3-5.8.3-5.7 6-.3c8.7-.3 9.8-.7 15.7-5.8 8.6-7.4 10.6-8.1 32.4-10.9 28.8-3.6 32.2-3.6 36.6-.3 2 1.5 4.5 4.7 5.7 7 3.6 7.5 17.2 75.7 26.7 133.5 2.6 16.1 5.2 29.3 5.6 29.3 1.3 0 1-2.6-4.6-36.2-6.4-37.6-12.7-71.1-19.5-103-4.2-19.4-5.4-23.3-8-27.2-1.8-2.5-4.5-5.3-6.1-6.1-4.1-2.1-15.8-1.8-38 1.1z"); _clickablePaths.Add((linePath, "14_line", SKColors.Black)); // 蓝色区域(region 14) var restPath = SKPath.ParseSvgPathData("M133.5 112.6c-1.1.2-10.5 1.3-21 2.5-21.1 2.5-22.9 3.1-32.2 10.8-6 5-6.2 5.1-12.6 5.1-9.3 0-9.7.3-9.7 6.6v5.4h5.3c20.2 0 41.1 14.8 49.3 35 3.6 8.8 4.6 27.4 1.9 36.6-5.5 19.3-20.6 33.9-40 38.9-3.2.8-7.6 1.5-9.7 1.5H61v29h32v-20.1l4.3.3 4.2.3.3 9.7.3 9.8H186l-.5-2.8c-.3-1.5-3.2-18.7-6.5-38.2-5.9-35.7-13.2-73.8-19.6-103.5-5.3-24.6-10-29.4-25.9-26.9zM144 255v11h-8v-22h8v11z"); _clickablePaths.Add((restPath, "14_rest", StringToColor("#6699FF"))); } using (SKPaint paint = new SKPaint()) { foreach (var pathItem in _clickablePaths) { // 根据区域设置绘制样式 paint.Style = pathItem.RegionId is "73" or "14_line" ? SKPaintStyle.Stroke : SKPaintStyle.StrokeAndFill; paint.Color = pathItem.CurrentColor; paint.StrokeWidth = 1; canvas.DrawPath(pathItem.Path, paint); } } }
4. 实现点击识别与颜色切换
添加点击事件处理方法,转换触摸坐标并判断点击的路径,修改颜色后触发重绘:
private void OnTapGestureRecognizerTapped(object sender, EventArgs e) { if (sender is not SKCanvasView canvasView || e is not TappedEventArgs tappedArgs) return; // 将Xamarin视图坐标转换为Skia画布坐标 var xamarinPoint = tappedArgs.GetPosition(canvasView); var scale = canvasView.CanvasSize.Width / canvasView.Width; var skPoint = new SKPoint((float)(xamarinPoint.X * scale), (float)(xamarinPoint.Y * scale)); // 遍历路径判断点击区域 foreach (var pathItem in _clickablePaths) { bool isHit; if (pathItem.RegionId is "73" or "14_line") { // 描边路径需要传入笔触宽度进行精确判断 using var paint = new SKPaint { StrokeWidth = 1 }; isHit = pathItem.Path.Contains(skPoint, paint); } else { // 填充路径直接判断点是否在内部 isHit = pathItem.Path.Contains(skPoint); } if (isHit) { // 切换颜色(示例:点击变红,再点击恢复原色) pathItem.CurrentColor = pathItem.CurrentColor == SKColors.Red ? GetOriginalColor(pathItem.RegionId) : SKColors.Red; // 触发画布重绘 canvasView.InvalidateSurface(); break; } } } // 获取区域原始颜色的辅助方法 private SKColor GetOriginalColor(string regionId) { return regionId switch { "72" => SKColors.Black, "73" => StringToColor("#E7FFFF"), "14_line" => SKColors.Black, "14_rest" => StringToColor("#6699FF"), _ => SKColors.Transparent }; }
关键注意事项
- 路径持久化:必须保存每个
SKPath实例,不能在绘制后丢弃,否则无法进行点击包含测试。 - 坐标转换:Xamarin的触摸坐标是视图相对坐标,需要按画布缩放比例转换为Skia绝对坐标,避免点击判断偏移。
- 描边路径判断:对于描边样式的路径,需使用
Contains(SKPoint, SKPaint)重载,传入与绘制时一致的笔触宽度,确保点击范围准确。
内容的提问来源于stack exchange,提问作者GuidoG
相关产品推荐
相关产品推荐

