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

如何用.NET MAUI Graphics绘制可挂果的交互式树形图

.NET MAUI SkiaSharp 交互式树形图实现方案

需求概述

需要实现一个交互式树形图,允许用户在树上悬挂果实,具体要求:

  • 使用.NET MAUI Graphics(SkiaSharp)绘制树形图
  • 添加可唤起上下文菜单的加号图标,支持指定挂果数量
  • 此前尝试GridView未成功,现有原型代码存在画布不重绘问题

现有问题代码

XAML部分

<skia:SKCanvasView x:Name="skiaView"
                  WidthRequest="600"
                  HeightRequest="400"
                  EnableTouchEvents="True"
                  PaintSurface="OnPaintSurface"
                  Touch="OnTouch" />

C#代码

private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
    var canvas = e.Surface.Canvas;
    canvas.Clear(SKColors.White);

    // Draw the tree SVG
    canvas.DrawPicture(_treeSvg.Picture);
}

private void OnTouch(object sender, SKTouchEventArgs e)
{
    if (e.ActionType == SKTouchAction.Pressed)
    {
        // Check if the touch point intersects with any branching rectangle
        ShowContextMenu(e.Location);
    }
}

private void RenderSvgAtPoint(SKSvg svg, SKPoint location)
{
    MainThread.BeginInvokeOnMainThread(() =>
    {
        using (var surface = SKSurface.Create(new SKImageInfo((int)skiaView.CanvasSize.Width, (int)skiaView.CanvasSize.Height)))
        {
            var canvas = surface.Canvas;

            //// Draw existing SVG
            canvas.DrawPicture(_treeSvg.Picture);

            // Draw new SVG at the selected location
            canvas.DrawPicture(svg.Picture, location.X, location.Y);
        }

        skiaView.InvalidateSurface();
    });
}

private async void ShowContextMenu(SKPoint location)
{
    var options = new string[] { "RENDER APPLE" };

    // Display context menu
    var action = await DisplayActionSheet("Choose an option", null, null, options);

    // Handle selected option
    if (action == options[0])
    {
        var newSvg = new SKSvg();
        using (var stream = GetType().Assembly.GetManifestResourceStream("MauiApp1.Resources.Images.apple.svg"))
        {
            newSvg.Load(stream);
            RenderSvgAtPoint(newSvg, location);
        }
    }
}

解决方案

1. 修复画布不重绘问题

现有代码未持久化已添加的果实状态,每次重绘只会渲染树形图。通过集合存储果实数据,重绘时统一渲染:

  • 添加全局集合存储果实信息:
private List<(SKSvg FruitSvg, SKPoint Position)> _fruits = new();
private SKSvg _appleSvg; // 预加载苹果SVG避免重复IO操作
  • 预加载苹果SVG(在页面OnAppearing中执行):
protected override void OnAppearing()
{
    base.OnAppearing();
    _appleSvg = new SKSvg();
    using (var stream = GetType().Assembly.GetManifestResourceStream("MauiApp1.Resources.Images.apple.svg"))
    {
        _appleSvg.Load(stream);
    }
}
  • 修改OnPaintSurface方法,遍历集合绘制所有果实:
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
    var canvas = e.Surface.Canvas;
    canvas.Clear(SKColors.White);

    // 绘制树形图
    canvas.DrawPicture(_treeSvg.Picture);

    // 绘制所有已添加的果实(调整位置让苹果中心对准触摸点)
    foreach (var fruit in _fruits)
    {
        var drawPos = new SKPoint(
            fruit.Position.X - _appleSvg.Picture.CullRect.Width / 2,
            fruit.Position.Y - _appleSvg.Picture.CullRect.Height / 2
        );
        canvas.DrawPicture(fruit.FruitSvg.Picture, drawPos.X, drawPos.Y);
    }

    // 绘制加号图标
    DrawPlusIcon(canvas);
}
  • 重构添加果实的方法:
private void AddFruitAtPoint(SKPoint location, int count = 1)
{
    for (int i = 0; i < count; i++)
    {
        // 给多颗果实添加微小偏移避免重叠
        var offsetPos = new SKPoint(
            location.X + (i * 10),
            location.Y + (i * 10)
        );
        _fruits.Add((_appleSvg, offsetPos));
    }
    skiaView.InvalidateSurface();
}

2. 实现加号图标与指定数量功能

  • 添加绘制加号图标的方法:
private void DrawPlusIcon(SKCanvas canvas)
{
    var iconSize = 40f;
    var pos = new SKPoint(skiaView.CanvasSize.Width - iconSize - 20, 20);

    using var paint = new SKPaint
    {
        Color = SKColors.Green,
        StrokeWidth = 4,
        IsStroke = true,
        StrokeCap = SKStrokeCap.Round
    };

    // 绘制横杠与竖杠
    canvas.DrawLine(pos.X, pos.Y + iconSize/2, pos.X + iconSize, pos.Y + iconSize/2, paint);
    canvas.DrawLine(pos.X + iconSize/2, pos.Y, pos.X + iconSize/2, pos.Y + iconSize, paint);
}
  • 修改触摸事件,区分点击加号或树干区域:
private void OnTouch(object sender, SKTouchEventArgs e)
{
    if (e.ActionType != SKTouchAction.Pressed) return;

    var iconSize = 40f;
    var iconRect = new SKRect(
        skiaView.CanvasSize.Width - iconSize - 20,
        20,
        skiaView.CanvasSize.Width - 20,
        20 + iconSize
    );

    // 点击加号图标唤起数量选择菜单
    if (iconRect.Contains(e.Location))
    {
        ShowFruitCountMenu();
        return;
    }

    // 点击树干区域添加单个果实
    AddFruitAtPoint(e.Location);
}
  • 添加数量选择上下文菜单:
private async void ShowFruitCountMenu()
{
    var options = new string[] { "1个", "3个", "5个" };
    var action = await DisplayActionSheet("选择挂果数量", "取消", null, options);

    int count = action switch
    {
        "1个" => 1,
        "3个" => 3,
        "5个" => 5,
        _ => 0
    };

    if (count > 0)
    {
        // 默认添加到树的中间位置,可改为让用户选择具体挂果点
        var defaultPos = new SKPoint(skiaView.CanvasSize.Width / 2, skiaView.CanvasSize.Height / 2);
        AddFruitAtPoint(defaultPos, count);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:52:43