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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:22:15