如何用.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
相关产品推荐
相关产品推荐

