Xamarin.Forms中用SkiaSharp为SVG内元素加触摸事件实现房间选中高亮
嘿,这个问题我之前也碰到过!用普通的TapGestureRecognizer确实没法精准命中SVG里的单个元素,得靠SkiaSharp的底层触摸事件和路径检测来实现。下面是具体的解决方案,一步一步来:
核心思路
SkiaSharp的SKCanvasView支持直接处理触摸事件,我们需要先把SVG中代表每个房间的路径元素提取出来,存储它们的SKPath对象和元数据;然后在触摸事件中,将屏幕坐标转换为SVG的坐标空间,判断点击点是否落在某个房间路径内;最后根据选中状态重新绘制高亮效果。
步骤1:加载SVG并提取房间路径
首先你需要把SVG里代表每个房间的**独立路径元素(SKPath)**提取出来,建议给每个房间的SVG元素加个id属性(比如id="room-1"),方便筛选。可以定义一个自定义类来存储每个房间的路径信息:
public class RoomPath { public string RoomId { get; set; } public SKPath Path { get; set; } public SKColor OriginalColor { get; set; } public bool IsSelected { get; set; } = false; }
然后加载SVG并提取目标路径:
private List<RoomPath> _roomPaths = new List<RoomPath>(); private SKSVG _svg; private void LoadFloorPlanSvg() { // 从资源文件加载SVG(替换成你的SVG资源路径) using (var stream = typeof(YourPage).Assembly.GetManifestResourceStream("YourApp.Resources.floorplan.svg")) { _svg = new SKSVG(); _svg.Load(stream); // 遍历SVG根节点的子元素,筛选房间路径 foreach (var element in _svg.Root.Children) { if (element is SKPath pathElement) { // 筛选带有room前缀id的元素 if (pathElement.TryGetAttribute("id", out var roomId) && roomId.StartsWith("room-")) { // 获取元素原始填充色 var originalColor = pathElement.Fill ?? SKColors.LightGray; _roomPaths.Add(new RoomPath { RoomId = roomId, Path = pathElement, OriginalColor = originalColor }); } } // 如果房间是组合元素(SKGroup),需要递归遍历子元素 else if (element is SKGroup group) { ExtractPathsFromGroup(group); } } } } // 递归提取组合元素中的路径 private void ExtractPathsFromGroup(SKGroup group) { foreach (var child in group.Children) { if (child is SKPath pathElement) { if (pathElement.TryGetAttribute("id", out var roomId) && roomId.StartsWith("room-")) { var originalColor = pathElement.Fill ?? SKColors.LightGray; _roomPaths.Add(new RoomPath { RoomId = roomId, Path = pathElement, OriginalColor = originalColor }); } } else if (child is SKGroup subGroup) { ExtractPathsFromGroup(subGroup); } } }
步骤2:处理触摸事件判断点击的房间
给SKCanvasView添加Touch事件,替换掉原来的TapGestureRecognizer。关键是要把屏幕触摸坐标转换为SVG的坐标空间,避免因为缩放/适配导致点击位置偏移:
private void CanvasView_Touch(object sender, SKTouchEventArgs e) { if (e.ActionType != SKTouchAction.Pressed || _roomPaths.Count == 0) { e.Handled = true; return; } var canvasView = sender as SKCanvasView; if (canvasView == null) return; // 计算SVG视口到Canvas的缩放矩阵,并反转得到坐标转换矩阵 var svgViewport = _svg.Root.ViewBox; var canvasBounds = new SKRect(0, 0, canvasView.CanvasSize.Width, canvasView.CanvasSize.Height); var scaleMatrix = SKMatrix.CreateScaleToFit(svgViewport, canvasBounds, SKMatrixResizeMode.None); scaleMatrix.Invert(out var inverseMatrix); // 将屏幕触摸点转换为SVG坐标空间的点 var svgTouchPoint = e.Location.ApplyMatrix(inverseMatrix); // 遍历所有房间路径,判断点击是否命中 foreach (var room in _roomPaths) { if (room.Path.Contains(svgTouchPoint.X, svgTouchPoint.Y)) { // 取消之前选中的房间 foreach (var r in _roomPaths) r.IsSelected = false; // 标记当前房间为选中状态 room.IsSelected = true; // 触发你的业务逻辑(比如通知ViewModel、更新UI) OnRoomSelected(room.RoomId); // 刷新Canvas绘制高亮 canvasView.InvalidateSurface(); break; } } e.Handled = true; } // 自定义选中回调方法 private void OnRoomSelected(string roomId) { // 这里可以添加你的业务逻辑,比如弹窗显示房间信息 Console.WriteLine($"选中房间:{roomId}"); }
步骤3:实现高亮绘制
在OnPaintSurface方法中,先绘制整个SVG作为背景,再根据IsSelected状态叠加绘制选中房间的高亮颜色:
private void CanvasView_PaintSurface(object sender, SKPaintSurfaceEventArgs e) { var canvas = e.Surface.Canvas; canvas.Clear(); var svgViewport = _svg.Root.ViewBox; var canvasBounds = e.Info.Rect; var scaleMatrix = SKMatrix.CreateScaleToFit(svgViewport, canvasBounds, SKMatrixResizeMode.None); // 1. 绘制原始SVG作为背景 canvas.Save(); canvas.Concat(scaleMatrix); _svg.Root.Draw(canvas); canvas.Restore(); // 2. 绘制选中房间的高亮效果 canvas.Save(); canvas.Concat(scaleMatrix); foreach (var room in _roomPaths) { using (var paint = new SKPaint()) { paint.Color = room.IsSelected ? SKColors.OrangeRed : room.OriginalColor; paint.Style = SKPaintStyle.Fill; paint.IsAntialias = true; // 可选:给选中房间加个边框 if (room.IsSelected) { paint.StrokeWidth = 2; paint.StrokeColor = SKColors.Black; paint.Style = SKPaintStyle.FillAndStroke; } canvas.DrawPath(room.Path, paint); } } canvas.Restore(); }
注意事项
- 如果SVG中的房间元素是嵌套的组合元素(
SKGroup),一定要递归遍历子元素提取路径,不然会漏掉。 - 坐标转换是关键,尤其是当CanvasView有布局变化(比如旋转、缩放)时,要确保矩阵计算准确。
- 对于复杂的SVG,路径数量较多时,可以在触摸事件中先做粗略的区域判断,减少遍历次数提升性能。
内容的提问来源于stack exchange,提问作者gjfonte
相关产品推荐
相关产品推荐

