You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
}

注意事项

  1. 如果SVG中的房间元素是嵌套的组合元素(SKGroup),一定要递归遍历子元素提取路径,不然会漏掉。
  2. 坐标转换是关键,尤其是当CanvasView有布局变化(比如旋转、缩放)时,要确保矩阵计算准确。
  3. 对于复杂的SVG,路径数量较多时,可以在触摸事件中先做粗略的区域判断,减少遍历次数提升性能。

内容的提问来源于stack exchange,提问作者gjfonte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:31:22