VisualTFT自定义圆形进度条:Canvas绘图与嵌入式GUI开发实践

news2026/5/22 13:53:12
1. 项目概述与核心价值最近在做一个工业HMI的项目客户要求在设备启动自检的界面上用一个圆环形的进度条来展示自检进度而不是传统的长条状进度条。他们觉得圆环看起来更“高级”也更符合他们产品的整体UI风格。接到这个需求我第一反应就是去翻VisualTFT的控件库结果发现官方自带的进度条控件只有水平ProgressBar和垂直VProgressBar两种。这可就有点意思了官方没提供但需求又很明确那就得自己动手“造轮子”了。这个“自定义圆形进度条”的需求在工控、智能家居中控屏、医疗仪器面板等嵌入式GUI开发里其实挺常见的。它不仅仅是把形状从矩形变成圆形那么简单背后涉及到图形绘制、数值映射、动画平滑度、以及如何与底层硬件比如单片机高效交互等一系列问题。VisualTFT作为一款优秀的嵌入式UI设计工具其强大的自定义控件和脚本功能恰恰给了我们实现这种个性化需求的舞台。今天我就把自己从零开始在VisualTFT里实现一个美观、实用、可复用的圆形进度条的全过程包括设计思路、关键脚本编写、属性封装以及实际调试中遇到的坑毫无保留地分享出来。无论你是刚接触VisualTFT的新手还是想深化控件自定义能力的老鸟相信这篇内容都能给你带来直接的参考价值。2. 整体设计思路与方案选型在VisualTFT里实现一个控件尤其是这种图形化控件通常有几条路可以走。我们先来拆解一下并说说我为什么选择了最终这个方案。2.1 可行性路径分析纯位图叠加方案准备0%-100%共101张圆环进度图片通过脚本控制显示哪一张。这种方法最简单粗暴效果也稳定因为图片是美工做好的。但缺点极其明显资源占用巨大101张图片进度不连续只能以1%为步进修改样式比如颜色、粗细需要重做所有图片完全不灵活。PASS。使用“仪表”控件模拟VisualTFT有一个Meter仪表控件本身就是一个圆环或扇形。我们可以将其刻度隐藏指针改成一个不显示的标记然后通过设置其值来改变填充区域。这个方法比方案1好但Meter控件的重点在于模拟仪表盘其API和属性对于“进度条”这个应用场景来说并不直观定制填充样式如渐变色也比较麻烦。使用“画布”控件动态绘制这是最灵活、最专业也是我最终采用的方案。VisualTFT提供了Canvas画布控件它就像一块空白的画布我们可以通过Lua脚本使用其提供的绘图API如画弧、画线、填充在上面动态地绘制出我们想要的任何图形。圆形进度条本质上就是一个不断变长的圆弧用Canvas来实现再合适不过。2.2 为什么选择“Canvas动态绘制”方案选择这个方案是基于以下几个核心考量极致灵活进度条的宽度、颜色包括静态色和渐变色、起始角度、绘制方向顺时针/逆时针、是否显示中心文本等全部可以通过属性或脚本参数控制无需修改资源。资源占用极小只需要一个Canvas控件几乎不占用额外的Flash存储空间图片资源特别适合资源紧张的嵌入式平台。平滑连续由于是实时计算并绘制图形进度可以非常平滑地变化甚至可以配合定时器实现动画过渡效果。技能复用掌握Canvas绘图就等于掌握了在VisualTFT中创建任何不规则图形控件的能力价值远超实现一个进度条本身。这个方案的核心在于编写正确的绘图逻辑。接下来我们就深入到Canvas控件的脚本中看看如何用代码“画”出这个圆环。3. 核心实现Canvas绘图脚本详解假设我们在VisualTFT的窗体上放置了一个Canvas控件命名为CanvasProgress。我们所有的魔法都将发生在它的onPaint事件回调函数里。这个函数会在控件需要重绘时如初始显示、值改变后被自动调用。3.1 绘图坐标与参数计算在屏幕上绘图首先要建立坐标系和理解关键参数。Canvas控件的左上角是坐标原点(0,0)向右为x轴正方向向下为y轴正方向。绘制一个圆环进度条我们需要以下几个核心参数centerX, centerY: 圆环的中心点坐标。通常就是Canvas宽度和高度的一半。radius: 圆环的半径。lineWidth: 圆环的粗细宽度。startAngle: 进度条开始的弧度角。数学上0弧度指向正右方3点钟方向。currentAngle: 当前进度对应的弧度角。这由当前进度值currentValue、最大值maxValue和最小值minValue计算得出。colorStart,colorEnd: 如果使用渐变色这是起始和结束颜色。让我们在onPaint函数中实现它。首先我们需要获取或定义这些参数。一种好的实践是将可配置的参数放在脚本的开头或者通过控件的自定义属性来设置后面会讲。这里我们先在脚本内定义。function CanvasProgress.onPaint(sender, vtx, paintParam) -- 1. 定义配置参数后续可改为从属性读取 local minValue 0 local maxValue 100 local currentValue 75 -- 示例当前进度75% local centerX sender.Width / 2 local centerY sender.Height / 2 local radius math.min(centerX, centerY) * 0.8 -- 半径为Canvas大小的80%留出边距 local lineWidth radius * 0.2 -- 圆环宽度为半径的20% local startAngle -math.pi / 2 -- 从顶部-90度即12点钟方向开始更符合视觉习惯 local endAngle startAngle (currentValue - minValue) / (maxValue - minValue) * (2 * math.pi) -- 计算结束弧度 local colorBack 0xCCCCCC -- 背景圆环颜色灰色 local colorFore 0x007ACC -- 前景进度颜色蓝色 -- 2. 绘制底层背景圆环 vtx:BeginPath() vtx:Arc(centerX, centerY, radius, 0, 2 * math.pi, false) -- 绘制一个完整的圆 vtx:SetLineWidth(lineWidth) vtx:SetStrokeColor(colorBack) vtx:Stroke() -- 3. 绘制上层进度圆环 vtx:BeginPath() -- 开始新路径 -- 绘制圆弧。参数中心x, 中心y, 半径, 起始角, 结束角, 是否逆时针(false为顺时针) vtx:Arc(centerX, centerY, radius, startAngle, endAngle, false) vtx:SetLineWidth(lineWidth) vtx:SetStrokeColor(colorFore) vtx:Stroke() -- 4. 可选绘制中心文本 local text string.format(%d%%, currentValue) vtx:SetFont(sender.FontName, sender.FontSize, vtx.FONT_BOLD) -- 使用控件字体或自定义 vtx:SetTextAlign(vtx.TEXT_ALIGN_CENTER, vtx.TEXT_ALIGN_MIDDLE) vtx:SetFillColor(0x000000) -- 文本颜色黑色 vtx:FillText(text, centerX, centerY) end关键点解析vtx:Arc()是绘图的核心。注意角度的单位是弧度不是角度。2 * math.pi就是一个完整的圆。我们绘制了两次第一次画一个完整的灰色圆环作为背景第二次根据进度画一个蓝色的圆弧作为前景。它们半径和宽度相同所以前景会覆盖背景形成进度效果。vtx:BeginPath()非常重要。它表示开始一条新的绘制路径。如果不调用第二次Arc会和第一次的路径连在一起导致绘制错误。进度计算(currentValue - minValue) / (maxValue - minValue)得到进度比例再乘以2 * math.pi整个圆的弧度得到进度对应的弧度跨度。3.2 实现渐变色与圆角端点基础的圆环有了但产品经理可能想要更炫酷的效果比如渐变色和圆润的线条端点。VisualTFT的Canvas绘图上下文也支持这些。添加渐变色 渐变色需要先创建一个线性或径向渐变对象然后将其设置为描边或填充样式。-- 在绘制前景进度圆环的部分替换掉单色设置 -- 创建线性渐变从进度起点到终点 local gradient vtx:CreateLinearGradient( centerX radius * math.cos(startAngle), -- 起点x centerY radius * math.sin(startAngle), -- 起点y centerX radius * math.cos(endAngle), -- 终点x centerY radius * math.sin(endAngle) -- 终点y ) gradient:AddColorStop(0.0, 0xFF0000) -- 0%位置为红色 gradient:AddColorStop(1.0, 0x0000FF) -- 100%位置为蓝色 vtx:BeginPath() vtx:Arc(centerX, centerY, radius, startAngle, endAngle, false) vtx:SetLineWidth(lineWidth) vtx:SetStrokeStyle(gradient) -- 使用渐变样式替代单一颜色 vtx:Stroke()设置圆角线帽 默认的线条端点是方形的vtx.LINE_CAP_BUTT。要让圆环的末端看起来圆润可以设置线帽为圆形。vtx:SetLineCap(vtx.LINE_CAP_ROUND) -- 在调用Stroke之前设置 vtx:Stroke()这样进度条的头部当前进度端点就会呈现一个半圆形视觉效果更加柔和。3.3 封装为可复用的自定义控件把上面的代码直接写在onPaint里可以工作但不好复用。最佳实践是创建一个自定义控件。这样我们可以像使用标准控件一样拖拽它到界面上然后在属性窗口里设置最小值、最大值、当前值、颜色等甚至可以在其他项目中直接导入使用。创建自定义控件在VisualTFT的“资源”窗口右键“自定义控件”-“新建”。命名为CircleProgressBar。添加自定义属性在自定义控件的属性编辑器中添加我们需要的属性例如MinValue(整数 默认0)MaxValue(整数 默认100)CurrentValue(整数 默认0)LineWidth(整数 默认10)StartAngle(浮点数 默认-90 单位度 方便理解)ColorBackground(颜色)ColorForeground(颜色)ShowText(布尔 是否显示中间百分比文本)编写控件的绘制脚本在自定义控件的onPaint事件中编写与我们之前类似的脚本但关键参数不再写死而是从self控件对象的属性中读取。function self.onPaint(sender, vtx, paintParam) -- 从自定义属性读取值 local minValue self.MinValue or 0 local maxValue self.MaxValue or 100 local currentValue self.CurrentValue or 0 -- 确保当前值在范围内 currentValue math.max(minValue, math.min(maxValue, currentValue)) local centerX sender.Width / 2 local centerY sender.Height / 2 local radius math.min(centerX, centerY) - (self.LineWidth or 10) / 2 -- 将角度从度转换为弧度 local startAngleRad math.rad(self.StartAngle or -90) local endAngleRad startAngleRad (currentValue - minValue) / (maxValue - minValue) * (2 * math.pi) -- ... 后续绘制代码与之前类似但颜色等使用 self.ColorBackground, self.ColorForeground ... -- 文本显示根据 self.ShowText 属性判断 end提供设置进度的方法我们还需要一个接口让外部脚本比如定时器或数据解析回调能更新进度。可以在自定义控件中添加一个Lua函数。-- 在自定义控件的脚本中可以定义全局函数 function SetProgress(newValue) self.CurrentValue newValue self:Invalidate() -- 标记控件需要重绘触发onPaint end这样在其他地方就可以用CircleProgressBar.SetProgress(50)来更新进度了。4. 高级功能与性能优化一个基本的圆形进度条已经完成。但在实际工业项目中我们往往需要更多功能和考虑性能。4.1 动画平滑过渡直接从0%跳到100%会很生硬。我们可以实现一个平滑的动画过渡。思路使用一个Timer定时器控件。当目标进度改变时启动定时器。在定时器的onTimer事件中让CurrentValue逐步逼近TargetValue每次逼近都调用Invalidate()重绘。-- 在自定义控件或窗体脚本中 local targetValue 0 local animationSpeed 2 -- 每帧变化的单位值 function StartAnimationTo(newTarget) targetValue newTarget -- 启动一个间隔50ms的定时器 TimerAnimation.Enabled true end function TimerAnimation.onTimer(sender) local current CircleProgressBar.CurrentValue if math.abs(current - targetValue) 0.5 then -- 接近目标停止动画 sender.Enabled false CircleProgressBar.CurrentValue targetValue CircleProgressBar:Invalidate() else -- 向目标值移动 if current targetValue then CircleProgressBar.CurrentValue current animationSpeed else CircleProgressBar.CurrentValue current - animationSpeed end CircleProgressBar:Invalidate() end end注意动画会频繁触发重绘对性能有影响。在低性能MCU上需谨慎使用或降低动画帧率增大定时器间隔。4.2 多段颜色与阈值警示在工业场景进度可能代表温度、压力等。我们常常需要根据不同的值域显示不同颜色如正常蓝色、警告黄色、危险红色。实现方法在onPaint绘制前景圆弧前根据currentValue判断所在区间动态决定使用的颜色。local function getColorByValue(val) if val 60 then return 0x00CC00 -- 绿色安全 elseif val 85 then return 0xFFAA00 -- 黄色警告 else return 0xFF0000 -- 红色危险 end end local progressColor getColorByValue(currentValue) vtx:SetStrokeColor(progressColor)更复杂的可以配置一个颜色阈值表实现高度可配置化。4.3 性能优化要点在资源受限的嵌入式设备上GUI绘制是性能热点。减少不必要的重绘只在CurrentValue真正改变时调用Invalidate()。避免在循环或高频定时器中无条件重绘。简化绘图操作如果不需要背景圆环就不画。如果文本不常变可以考虑将其画在另一个Label控件上而不是每次在Canvas里绘制。固定大小与坐标如果Canvas控件大小和位置不变避免在onPaint中进行复杂的布局计算。可以将centerX,centerY,radius等计算一次后缓存起来注意当控件大小改变时需要重新计算可监听onResize事件。慎用渐变和透明度渐变色计算和Alpha混合透明度会消耗更多CPU资源。如果性能吃紧优先使用纯色。5. 常见问题与调试技巧在实际开发中你可能会遇到下面这些问题。5.1 圆环绘制不完整或位置不对现象圆环只显示一部分或者偏离中心。排查检查坐标和半径确保centerX, centerY是sender.Width/2和sender.Height/2。radius不能大于min(centerX, centerY) - lineWidth/2否则部分圆弧会画到控件区域外被裁剪。检查角度确认startAngle和endAngle的单位是弧度。一个常见错误是直接用了角度值。使用math.rad(角度)进行转换。验证Canvas大小在界面上检查Canvas控件是否被其他控件遮挡或其Width和Height属性是否设置正确。5.2 进度更新后界面无变化现象修改了CurrentValue但屏幕上进度条没动。排查是否调用了Invalidate()修改属性后必须调用控件的Invalidate()方法来请求重绘。直接改属性值是不会刷新屏幕的。作用域问题确保你修改的是正确的控件对象。在Lua脚本中使用控件的准确名称如CanvasProgress.CurrentValue 50。数值范围检查CurrentValue是否在MinValue和MaxValue之间。我们的绘图计算依赖这个范围。5.3 自定义控件属性不生效现象在属性窗口改了自定义控件的颜色、宽度但运行时没变化。排查属性读取在onPaint脚本中你是否通过self.PropertyName正确读取了自定义属性属性名必须完全匹配。属性默认值在自定义控件编辑器中检查属性是否设置了有效的默认值。运行时与设计时有时设计时属性面板的更改需要重新编译或下载工程到模拟器/实机才能生效。确保你执行了完整的“生成代码”-“下载”流程。5.4 实机运行闪烁或卡顿现象在PC模拟器上流畅下载到真机如STM32上画面闪烁或更新很慢。排查帧率过高检查动画定时器的间隔是否太短。对于许多工控MCU50ms20FPS已经是比较高的要求了可以尝试调整到100ms甚至200ms。绘图复杂度关闭渐变、圆角等高级效果看是否改善。如果改善明显说明需要优化绘图指令或降低效果。MCU性能与内存确认目标MCU的Flash和RAM资源是否充足。使用VisualTFT的性能分析工具如果有或查看编译报告了解资源占用情况。双缓冲检查VisualTFT工程或底层GUI库是否开启了双缓冲。双缓冲可以极大减少闪烁。通常这是在工程配置或底层驱动中设置的。调试技巧善用“输出窗口”在关键位置使用print(“当前值”, currentValue)将变量打印出来这是最直接的调试方法。分步绘制在onPaint函数中先注释掉绘制前景或背景的代码只画一样看是否正确。逐步增加功能定位问题代码。模拟器优先绝大部分逻辑和显示问题都在PC模拟器上解决再下载到真机调试硬件相关问题能大大提高效率。最后这个自定义圆形进度条控件完成后它的价值不仅仅在于完成了一个任务。它更是一个模板你可以基于它轻松修改出“扇形进度条”、“仪表盘指针”、“速度表”等各种各样的自定义图形控件。掌握Canvas绘图和自定义控件封装你在VisualTFT上的开发能力就上了一个大台阶面对各种奇葩的UI需求时心里都会更有底气。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2634935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…