MAUI中绘制带填充区域圆形的问题求助
MAUI带填充区域圆形图表绘制问题
我需要在MAUI中实现带填充区域的圆形图表(目标为绿色填充起始到结束时间的扇形区域),但实际绘制后圆形右侧出现异常填充区域。同时我不清楚AddArc方法的起始角度、结束角度以及最后一个clockwise参数的作用。
以下是实现IDrawable接口的绘制类代码,以及自定义控件的XAML代码:
绘制类代码
public class ElapsedTimeDrawable : IDrawable { public DateTime StartTime { private get; set; } public DateTime EndTime { private get; set; } public void Draw(ICanvas canvas, RectF dirtyRect) { canvas.StrokeColor = Color.FromRgba("6599ff"); canvas.StrokeSize = 5; canvas.DrawArc(15, 15, dirtyRect.Width - 30, dirtyRect.Height - 30, 0, 360, false, false); canvas.StrokeColor = Colors.Silver; canvas.StrokeSize = 1; canvas.DrawLine(dirtyRect.Width / 2, 10, dirtyRect.Width / 2, dirtyRect.Height - 10); canvas.DrawLine(10, dirtyRect.Height / 2, dirtyRect.Width - 10, dirtyRect.Height / 2); canvas.DrawString("0 h", dirtyRect.Width / 2, 0, HorizontalAlignment.Center); canvas.DrawString("6 h", dirtyRect.Width, dirtyRect.Height / 2, HorizontalAlignment.Center); canvas.DrawString("12 h", dirtyRect.Width / 2, dirtyRect.Height, HorizontalAlignment.Center); canvas.DrawString("18 h", 0, dirtyRect.Height / 2, HorizontalAlignment.Center); canvas.StrokeColor = Colors.Green; canvas.DrawPath(GetPathForTime(StartTime, dirtyRect)); canvas.StrokeColor = Colors.Red; canvas.DrawPath(GetPathForTime(EndTime, dirtyRect)); canvas.FillColor = Colors.LightGreen; canvas.FillPath(GetPathForShift(dirtyRect), WindingMode.EvenOdd); } private static PointF GetPoint(double hours, RectF dirtyRect) { double radio = (dirtyRect.Width - 30) / 2; // Tira la línea a la hora de inicio double theta; double y; double x; if (hours >= 0 && hours <= 6) // cuadrante superior derecha { theta = hours * 360 / 24; y = dirtyRect.Height - radio * Math.Sin(theta * Math.PI / 180); // El positivo del Y va hacia abajo x = dirtyRect.Width / 2 + Math.Sqrt(Math.Pow(radio, 2) - Math.Pow(y - dirtyRect.Height / 2, 2)); } else if (hours > 6 && hours <= 12) // cuadrante inferior derecha { theta = hours * 360 / 24 - 90; y = dirtyRect.Height / 2 + radio * Math.Sin(theta * Math.PI / 180); // El positivo del Y va hacia abajo x = dirtyRect.Width / 2 + Math.Sqrt(Math.Pow(radio, 2) - Math.Pow(y - dirtyRect.Height / 2, 2)); } else if (hours > 12 && hours <= 18) // cuadrante inferior izquierda { theta = 360 - hours * 360 / 24 - 90; y = dirtyRect.Height / 2 + radio * Math.Sin(theta * Math.PI / 180); // El positivo del Y va hacia abajo x = dirtyRect.Width / 2 - Math.Sqrt(Math.Pow(radio, 2) - Math.Pow(y - dirtyRect.Height / 2, 2)); } else // cuadrante superior izquierda { theta = 90 - (360 - hours * 360 / 24); y = dirtyRect.Height / 2 - radio * Math.Sin(theta * Math.PI / 180); // El positivo del Y va hacia abajo x = dirtyRect.Width / 2 - Math.Sqrt(Math.Pow(radio, 2) - Math.Pow(y - dirtyRect.Height / 2, 2)); } return new PointF((float)x, (float)y); } private static PathF GetPathForTime(DateTime time, RectF dirtyRect) { PathF path = new(); path.MoveTo(dirtyRect.Center.X, dirtyRect.Center.Y); path.LineTo(GetPoint(time.TimeOfDay.TotalHours, dirtyRect)); return path; } private PathF GetPathForShift(RectF dirtyRect) { PathF path = new(); double ahora = EndTime.TimeOfDay.TotalHours; PointF firstPoint = GetPoint(StartTime.TimeOfDay.TotalHours, dirtyRect); path.MoveTo(dirtyRect.Center.X, dirtyRect.Center.Y); path.LineTo(firstPoint); float startAngle = Math.Abs(90 - (float)(StartTime.TimeOfDay.TotalHours * 360 / 24)); float endAngle = Math.Abs(90 - (float)(ahora * 360 / 24)); path.AddArc(15, 15, dirtyRect.Width - 30, dirtyRect.Height - 30, startAngle, endAngle, true); path.LineTo(dirtyRect.Center.X, dirtyRect.Center.Y); return path; } }
自定义控件XAML代码
<ContentView x:Class="TimeApp.Controls.ShiftControl" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://schemas.microsoft.com/dotnet/2021/maui/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:TimeApp.Controls" xmlns:drawables="clr-namespace:TimeApp.Controls" mc:Ignorable="d"> <ContentView.Resources> <drawables:ElapsedTimeDrawable x:Key="shift" /> </ContentView.Resources> <VerticalStackLayout> <GraphicsView x:Name="ProgressView" Drawable="{StaticResource shift}" BackgroundColor="{StaticResource Primary}" HeightRequest="200" WidthRequest="200"/> </VerticalStackLayout> </ContentView>
问题解决与参数说明
1. AddArc参数说明
MAUI中Path.AddArc的参数含义:
- 前4个参数:圆弧的外接矩形(
x,y,width,height),定义圆弧所在的圆形范围 startAngle:圆弧起始角度,MAUI以右侧为0度,角度顺时针递增(0°朝右、90°朝下、180°朝左、270°朝上)endAngle:圆弧结束角度clockwise:布尔值,指定是否按顺时针方向绘制圆弧;若结束角度小于起始角度,设为false会绘制逆时针的小圆弧,避免绕整圆导致异常填充
2. 代码修改方案
简化GetPoint计算(替换原方法)
原分象限计算易出错,改用标准极坐标转换:
private static PointF GetPoint(double hours, RectF dirtyRect) { float radius = (dirtyRect.Width - 30) / 2; PointF center = dirtyRect.Center; // 每小时对应15°(360°/24),转换为MAUI标准角度(0°朝右),0h对应顶部(270°) double angleDegrees = hours * 15 + 270; double angleRadians = angleDegrees * Math.PI / 180; float x = center.X + (float)(radius * Math.Cos(angleRadians)); float y = center.Y + (float)(radius * Math.Sin(angleRadians)); // MAUI Y轴向下,直接加正弦值 return new PointF(x, y); }
修复GetPathForShift路径绘制(替换原方法)
private PathF GetPathForShift(RectF dirtyRect) { PathF path = new(); PointF center = dirtyRect.Center; RectF arcRect = new RectF(15, 15, dirtyRect.Width - 30, dirtyRect.Height - 30); double startHours = StartTime.TimeOfDay.TotalHours; double endHours = EndTime.TimeOfDay.TotalHours; // 计算MAUI标准起始/结束角度 float startAngle = (float)(startHours * 15 + 270) % 360; float endAngle = (float)(endHours * 15 + 270) % 360; // 判断绘制方向:结束角度小于起始角度时,用逆时针绘制小圆弧 bool clockwise = endAngle >= startAngle; path.MoveTo(center); path.LineTo(GetPoint(startHours, dirtyRect)); path.AddArc(arcRect, startAngle, endAngle, clockwise); path.LineTo(center); path.Close(); // 闭合路径,确保填充区域正确 return path; }
3. 修改核心点
- 用标准极坐标替换复杂的分象限计算,避免角度转换错误
- 适配MAUI的角度规则,确保刻度与绘制位置对应
- 自动判断圆弧绘制方向,解决跨0度时间段的异常填充问题
- 添加
path.Close()确保路径闭合,填充区域准确
内容的提问来源于stack exchange,提问作者jstuardo
相关产品推荐
相关产品推荐

