news 2026/9/9 16:22:00

Avalonia Canvas 绘图实战:用纯 XAML 画出一个跨平台速度表盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Avalonia Canvas 绘图实战:用纯 XAML 画出一个跨平台速度表盘

Avalonia Canvas 绘图实战:用纯 XAML 画出一个跨平台速度表盘

【免费下载链接】AvaloniaDevelop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The future of .NET UI项目地址: https://gitcode.com/GitHub_Trending/ava/Avalonia

面向 .NET 新手的一节 Avalonia 图形绘制练习:只写 XAML,在 Canvas 上画 2D 图形,最后得到一个可拖动的速度表盘。全程不写 C# 逻辑代码,同一份文件在 Windows、macOS、Linux 上渲染结果一致。

先把 Canvas 坐标系想清楚

Canvas 画 2D 图形前,先约定四件事,后面所有数字都按这套规则算:

约定规则
原点画板左上角是(0,0)
x 轴向右增大,150,150就是"向右 150、向下 150"
y 轴向下增大(和数学坐标系相反,按屏幕习惯)
单位DIP(设备无关像素),支持小数

Canvas 本身不做任何自动排列——它只认四个附加属性,把每个子元素"钉"在指定坐标上;子元素多宽由它自己的Width或内容决定,Canvas 不管。

附加属性锚定方向行为
Canvas.Left左边缘元素左边距画板左边的像素数
Canvas.Top上边缘元素顶边距画板上边的像素数
Canvas.Right右边缘元素右边距画板右边;宽度变化时位置跟着滑
Canvas.Bottom下边缘适合"贴底"元素

冲突规则只有一条:Left 和 Right 都写时 Left 赢,Top 和 Bottom 都写时 Top 赢;全都不写,元素落在(0,0)。另外 Canvas 测量自己时返回0,0,不占父容器空间,可以单独叠在任何面板上当纯绘图层。这些定义在 src/Avalonia.Controls/Canvas.cs 里,读一遍源码十分钟能看完。

该画哪种轮廓,就选哪个图形控件

选型不用背,对着这张表走:

你想画用这个控件必须写的属性
一条直线(刻度、指针)LineStartPointEndPoint
直角矩形RectangleWidthHeight
圆角矩形Rectangle同上,再加RadiusXRadiusY
圆或椭圆EllipseWidthHeight(两者相等即正圆)
一条折线Polyline/PolygonPoints
任意轮廓、曲线PathData(命令字符串)

这五个控件(外加ArcSector)都在 src/Avalonia.Controls/Shapes/ 目录下。它们共享同一套外观属性:Fill管内色,Stroke管边框色,StrokeThickness管边框粗细。Line默认边框粗细是 1,想画粗线记得显式写。

PathData是几何迷你语言的字符串,记住四个命令就够画大多数轮廓:

命令含义例子
M画笔移到起点,不画线M 50,100
L从当前位置画直线到指定点L 150,100
C三次贝塞尔曲线:两个控制点 + 终点C 100,50 130,150 200,100
Z直线连回起点并闭合,闭合影响FillZ

动手:把表盘画出来

目标:300×300 的表盘,中心(150,150),灰色外圈、白色盘面、8 条刻度、一根红指针,下面跟一个滑块和实时读数。

第一步:表盘底与边框。不画"圆环",而是叠两个Ellipse:灰色大圆直径 260,白色小圆直径 244,两层之间自然露出 8 像素灰边,省掉了画环形的任何额外工作。

<!-- 外圈灰底:距画板每边 20 --> <Ellipse Canvas.Left="20" Canvas.Top="20" Width="260" Height="260" Fill="#D3D3D3"/> <!-- 白色盘面:再内缩 8px,两圈之间形成环形边框 --> <Ellipse Canvas.Left="28" Canvas.Top="28" Width="244" Height="244" Fill="White"/>

第二步:指针和中心轴。指针是从中心(150,150)指向右上 45° 的一根红线,终点取半径 105 处的(224,76)。注意这里指针写在刻度之前——默认"后写在上",指针会被后面的刻度盖住,所以显式给它ZIndex="1"抬回来。中心再压一个 16×16 小红点,盖住线条汇聚处,看起来像转轴。

<!-- 指针:中心到 45° 方向,ZIndex=1 保证压过后面写的刻度 --> <Line StartPoint="150,150" EndPoint="224,76" Stroke="Red" StrokeThickness="5" ZIndex="1"/> <!-- 中心轴:盖住线条汇聚点 --> <Ellipse Canvas.Left="142" Canvas.Top="142" Width="16" Height="16" Fill="Red" ZIndex="2"/>

第三步:刻度、读数、滑块。8 条刻度沿 0°/45°/90°… 摆开,每条从半径 120 画到半径 105(长 15),45° 方向的坐标用150 ± r×0.707心算即可。读数不放属性里,而是绑定给滑块:滑块取值 0–200,初始 60,StringFormat把数字格式化成带单位的文本。把Value="60"改成 120,启动时读数就是 120 km/h。

完整案例(新建 Avalonia 项目后,把MainWindow.axaml<Window>标签内的内容整体换成下面这段即可运行):

<StackPanel Margin="20" Spacing="12" HorizontalAlignment="Center"> <!-- ① 表盘:Canvas 只负责定位,不做排列 --> <Canvas Width="300" Height="300"> <Ellipse Canvas.Left="20" Canvas.Top="20" Width="260" Height="260" Fill="#D3D3D3"/> <Ellipse Canvas.Left="28" Canvas.Top="28" Width="244" Height="244" Fill="White"/> <!-- ② 指针:写在刻度前,靠 ZIndex 抬到最上层 --> <Line StartPoint="150,150" EndPoint="224,76" Stroke="Red" StrokeThickness="5" ZIndex="1"/> <Ellipse Canvas.Left="142" Canvas.Top="142" Width="16" Height="16" Fill="Red" ZIndex="2"/> <!-- ③ 8 条刻度:每 45° 一条,半径 120 → 105 --> <Line StartPoint="150,30" EndPoint="150,45" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="235,65" EndPoint="224,76" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="270,150" EndPoint="255,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="235,235" EndPoint="224,224" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="150,270" EndPoint="150,255" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="65,235" EndPoint="76,224" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="30,150" EndPoint="45,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="65,65" EndPoint="76,76" Stroke="Black" StrokeThickness="2"/> </Canvas> <!-- ④ 读数:绑定滑块,拖动实时刷新 --> <TextBlock Text="{Binding ElementName=speedSlider, Path=Value, StringFormat='{}{0:0} km/h'}" FontSize="18" HorizontalAlignment="Center"/> <!-- ⑤ 滑块:0–200,初始 60 --> <Slider x:Name="speedSlider" Width="260" Minimum="0" Maximum="200" Value="60" HorizontalAlignment="Center"/> </StackPanel>

跑起来拖动滑块,读数跟着跳——图形、文字、控件各管各的,绑定只牵线。

画完第一张后最常问的 4 个问题

指针被后面的元素挡住了,怎么办?XAML 里"后写的默认在上"。想打破这个顺序就写ZIndex,它定义在Visual上(见 src/Avalonia.Base/Visual.cs),所有图形和控件都能用,数值越大越靠上。把指针的1改成-1再运行,能亲眼看到它被刻度盖住。

怎么让图形跟着滑块动?三个要素:ElementName找到源控件、Path=Value取哪个属性、StringFormat控制显示格式。案例里读数绑的是Text;换成绑EllipseWidthHeight,就能让圆跟着滑块变大变小:Width="{Binding ElementName=speedSlider, Path=Value, FallbackValue=80}"

小图形一多就卡,怎么提速?合并。上百条短刻度别用上百个Line,一个Path就能装下全部子路径:

<!-- 一条 Data 装多条子路径,渲染时只当一个图形处理 --> <Path Stroke="Black" StrokeThickness="2" Data="M 150,30 L 150,45 M 270,150 L 255,150 M 30,150 L 45,150"/>

绘制对象数量降下来了,重复的小元素尤其受益。

ZIndex明明设了,层序却像没变?ZIndex只在同一父容器内的兄弟元素之间重新排序。两个元素分属不同容器时,彼此完全不受影响——把其中一个挪进另一个的父级下,或者调整嵌套结构,层序才会按预期变化。

带走三句话,再往哪走

带走三句话:

  1. 原点在左上角,x 向右、y 向下,单位 DIP;Canvas.Left/Top钉位置,Left/Top 冲突时赢。
  2. 直线用Line、矩形用Rectangle(圆角加RadiusX/RadiusY)、圆用Ellipse、其余一切交给Path
  3. 默认后写的在上,要打破顺序就ZIndex,且只在同父级兄弟间生效。

继续往哪走:

  • 让指针真的转起来:给Line挂一个RotateTransform,用RotateTransform.AnglePropertyDoubleAnimation,再加一个十几行的IValueConverter把滑块值映射成角度,表盘就从"静态图"变成"活的仪表"。
  • 复杂轮廓:命令字符串拼不动时,改用StreamGeometry在代码里逐段构造路径,XAML 只负责展示。
  • 动画细节:补间、关键帧、缓动函数都在 src/Avalonia.Base/ 的 Animation 目录里,照着声明式写法加到表盘上即可。

同一套 XAML,所有平台行为一致——这大概就是跨平台绘图的全部卖点。

【免费下载链接】AvaloniaDevelop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The future of .NET UI项目地址: https://gitcode.com/GitHub_Trending/ava/Avalonia

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 16:19:17

Nuclear 免费音乐播放器:从安装到进阶的完整指南

Nuclear 免费音乐播放器&#xff1a;从安装到进阶的完整指南 【免费下载链接】nuclear Streaming music player that finds free music for you 项目地址: https://gitcode.com/GitHub_Trending/nu/nuclear 还在为流媒体服务付月费&#xff0c;或者嫌免费渠道里的歌零零…

作者头像 李华
网站建设 2026/9/9 16:19:16

opencode不是工具名:解析常见报错与AI编程助手替代方案

1. “opencode”不是标准工具名&#xff1a;先厘清它到底指什么 “opencode”这个词在当前技术生态里&#xff0c; 没有官方定义、没有统一归属、没有权威文档 。它既不是 npm 官方注册的包名&#xff08; npm view opencode 返回 404&#xff09;&#xff0c;也不是 GitHu…

作者头像 李华
网站建设 2026/9/9 16:19:07

Flutter OHOS 更新Flutter插件项目结构

更新内容 flutter插件项目中的ohos目录&#xff0c;将从OpenHarmony工程project结构&#xff0c;替换为OpenHarmony工程module结构。flutter工程中引用的har文件&#xff0c;统一放到 ohos/har 目录下。更新后需要删除ohos插件中的旧模块目录。 更新步骤 以 flutter_flutter…

作者头像 李华
网站建设 2026/9/9 16:19:05

C11与C99混用编译导致嵌入式崩溃:ABI与结构体布局排查指南

上个月排查一个嵌入式网关的崩溃问题&#xff0c;前前后后折腾了三天。现象很诡异&#xff1a;同一份源代码&#xff0c;在 x86 的开发板上跑得稳稳当当&#xff0c;交叉编译到 ARM 目标板上一启动就随机死&#xff0c;多跑几次还会出“corrupted double-linked list”这类让人…

作者头像 李华
网站建设 2026/9/9 16:19:04

深度学习图像分类入门:从环境搭建到CNN实战

简介&#xff1a;一份适合初学者上手的深度学习图像分类教程&#xff0c;基于Python 2.7与PyTorch 0.4.1实现&#xff0c;面向遥感图像分类等入门任务。整个项目以包含21个类别、每类100张图像的纯净遥感数据集为基础&#xff0c;按8:2比例随机划分训练集与验证集&#xff0c;并…

作者头像 李华
网站建设 2026/9/9 16:17:38

JAVA毕设选题推荐:面向企业办公场景的知识产权管理系统设计与技术实现 Vue可视化前端结合SpringBoot后端的知识产权管控系统研发【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华