资讯动态

Qt电池进度条手绘实战:paintEvent几何对齐与状态切换

发布时间:2026/10/26 10:44:32 来源:尧图企业网站定制
简介这份资源面向Qt初学者与界面开发爱好者围绕paintEvent事件打造一个可复用的自定义电池进度条控件解决电量可视化与状态提示的绘制问题。压缩包共8个文件约7KB包含3个cpp与2个h源文件承载绘制与动画逻辑另有pro工程文件、ui界面文件及user配置结构精简便于直接编译运行。已有501人学习下载适合作为Qt绘图入门练手项目。内容覆盖QPainter绘制电池外壳与电量填充、按电量比例切换正常与警示配色、借助QPropertyAnimation实现电量变化动画并涉及paintEvent性能优化与布局管理思路读者可据此掌握自绘控件从初始化到状态反馈的完整流程理解绘图与动画逻辑分离的代码组织方式。1. 电池进度条不是画个矩形那么简单很多人第一次在 Qt 里做电池电量控件直觉就是拖一个 QProgressBar、改个颜色、设个圆角跑起来发现——电量 20% 的时候进度条是方的电池头是圆的两者对不上想加个充电闪电图标QProgressBar 根本不给你塞自定义图形的口子再想按电量变色绿→黄→红样式表写一堆::chunk选择器还未必生效。这就是为什么一线做嵌入式 HMI、设备面板、车载中控的团队最后都会回到paintEvent手绘这条路。这个资源给的就是一套基于QWidgetQPainter的电池进度条绘制实现核心解决三件事电量填充与电池外壳的几何对齐、按阈值动态换色、充电态与低电态的视觉区分。适合已经会写 Qt 界面、但被样式表卡住的开发者也适合刚学paintEvent想找一个完整可跑例子的新手。2. 拆开 paintEvent几何、路径与状态机2.1 为什么不用 QProgressBar 而自己画先把选型理由说清楚不然照着抄代码也是糊的。QProgressBar 的绘制模型是「槽 块」它的圆角、边框、填充色都绑在样式表的子控件上你能控制的粒度到::chunk为止。电池控件需要的视觉元素比这多外壳圆角矩形、右侧正极凸起、内部填充区还要留出内边距不能让填充贴到外壳边、充电闪电、低电闪烁。这些元素之间的相对位置是几何约束关系不是样式表能表达的。自己画的好处是坐标系完全归你管。paintEvent里拿到的QPainter以控件左上角为原点你只要把「电池外壳占多大、正极凸起多宽、填充区从哪到哪」算成一组矩形后面所有绘制都基于这组矩形做偏移缩放、改尺寸都不会错位。代价是你得自己处理抗锯齿、自己算圆角路径、自己在setValue时触发update()。这三件事都不难难的是第一次写的时候不知道从哪下手。我一般会把绘制拆成三层底层画外壳描边 正极中层画填充按百分比裁剪顶层画状态图标充电/低电。三层各自独立改一层不影响另外两层这是后面加功能不翻车的关键。2.2 几何参数怎么定一份能直接抄的尺寸表电池控件的坑几乎全在几何上。下面这张表是我实际调过的参数按控件高度H归一化宽度按比例推这样控件拉伸时不会变形。参数取值相对 H说明外壳高度0.6H上下留白避免贴边外壳宽度1.6 × 外壳高度常见电池长宽比可按需调正极凸起宽度0.08 × 外壳宽度太宽像积木太窄看不见正极凸起高度0.4 × 外壳高度垂直居中外壳圆角0.12 × 外壳高度圆角过大填充会露角填充内边距0.06 × 外壳高度填充不能贴外壳边填充圆角外壳圆角 − 内边距保证内外圆角同心这张表的价值在于「同心圆角」这一条。很多人填充区直接用一个固定圆角结果电量低的时候填充块很小圆角半径比填充块还大画出来是个胶囊甚至是个圆视觉上就崩了。正确做法是填充圆角 外壳圆角 − 内边距这样无论填充多宽内外弧线始终平行。正极凸起的位置也要注意它贴在外壳右侧垂直居中高度是外壳高度的 40%。如果你把凸起画成和外壳等高看起来就像个电容而不是电池。这个细节决定了控件「像不像电池」属于玄学但确实影响观感。2.3 核心绘制代码从外壳到填充下面这段是paintEvent的主体去掉业务逻辑后可以直接跑。我用QPainterPath画圆角矩形用setClipPath做填充裁剪这样填充区永远被限制在外壳内部不会溢出。void BatteryWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); // 1. 计算外壳矩形按高度归一化水平居中 const qreal H height(); const qreal shellH H * 0.6; const qreal shellW shellH * 1.6; const qreal shellX (width() - shellW - shellH * 0.08) / 2.0; const qreal shellY (H - shellH) / 2.0; QRectF shellRect(shellX, shellY, shellW, shellH); // 2. 正极凸起贴外壳右侧垂直居中 const qreal capW shellW * 0.08; const qreal capH shellH * 0.4; QRectF capRect(shellX shellW, shellY (shellH - capH) / 2.0, capW, capH); // 3. 外壳路径圆角矩形 const qreal radius shellH * 0.12; QPainterPath shellPath; shellPath.addRoundedRect(shellRect, radius, radius); // 4. 画外壳描边 QPen pen(m_borderColor, qMax(1.0, shellH * 0.04)); painter.setPen(pen); painter.setBrush(Qt::NoBrush); painter.drawPath(shellPath); painter.drawRoundedRect(capRect, capW * 0.3, capW * 0.3); // 5. 计算填充区内边距 按百分比裁剪 const qreal pad shellH * 0.06; QRectF fillRect shellRect.adjusted(pad, pad, -pad, -pad); const qreal fillW fillRect.width() * qBound(0.0, m_value / 100.0, 1.0); QRectF activeRect(fillRect.left(), fillRect.top(), fillW, fillRect.height()); // 6. 用外壳路径裁剪保证填充不越界 painter.save(); painter.setClipPath(shellPath); QPainterPath fillPath; const qreal fillRadius qMax(0.0, radius - pad); fillPath.addRoundedRect(activeRect, fillRadius, fillRadius); painter.fillPath(fillPath, m_fillColor); painter.restore(); // 7. 顶层状态图标充电/低电由子函数处理 drawStatusIcon(painter, shellRect); }逐段说逻辑。第 1 段把控件高度当基准算出外壳尺寸并水平居中注意shellX里减掉了shellH * 0.08这是给正极凸起预留的宽度否则整体会偏左。第 2 段正极凸起的高度取外壳的 40%垂直居中圆角用capW * 0.3让它看起来是圆头而不是方头。第 3、4 段画外壳描边宽度跟着高度走qMax(1.0, ...)是防止控件很小时线宽变成 0 导致不显示。第 5 段是核心fillRect是外壳内缩一个内边距后的区域fillW用qBound把百分比夹在 0 到 1 之间防止外部传入越界值把填充画到外壳外面。第 6 段用setClipPath(shellPath)做二次保险即使填充路径算错了也不会溢出外壳这是血泪经验——早期版本没加裁剪电量 100% 时填充的圆角会盖住外壳描边看起来像描边断了。参数上最需要调的是pad和radius的比例。pad太小填充贴边太大填充区显得空。我一般让pad在0.05H到0.08H之间试radius在0.10H到0.15H之间具体看你的 UI 风格偏硬朗还是偏圆润。2.4 状态切换充电、低电与颜色阈值电量控件不是静态的颜色要跟着电量走充电时还要有额外标识。常见做法是在setValue里判断阈值改m_fillColor然后update()。阈值我一般设三档大于 60% 绿色20% 到 60% 黄色低于 20% 红色。充电态单独用一个布尔量m_charging为真时在填充区中央画一个闪电并且颜色统一用绿色不跟电量阈值走——因为充电时用户关心的是「在充」不是「充到多少」。void BatteryWidget::setValue(int value) { value qBound(0, value, 100); if (value m_value) return; // 值没变不重绘省 CPU m_value value; if (m_charging) { m_fillColor QColor(0x2E, 0xC4, 0x6B); // 充电统一绿 } else if (m_value 60) { m_fillColor QColor(0x2E, 0xC4, 0x6B); } else if (m_value 20) { m_fillColor QColor(0xF2, 0xB1, 0x2B); } else { m_fillColor QColor(0xE0, 0x4B, 0x3D); } update(); // 触发 paintEvent }这里有个容易忽略的点if (value m_value) return;这行不是可有可无。设备面板上电量可能每秒刷新一次如果每次刷新都无条件update()即使值没变也会触发整条绘制链路CPU 占用会上去。加上这行判断后只有值真正变化才重绘实测在低端 ARM 板子上差别很明显。充电闪电的绘制放在drawStatusIcon里用QPainterPath画一个多边形坐标相对填充区中心偏移。低电状态除了红色我一般再加一个呼吸效果——用QPropertyAnimation改透明度或者用QTimer每 500ms 切换一次填充色的明度。呼吸效果不是必须的但设备面板上低电提示如果只是静态红色用户容易忽略这是实际用下来比较有效的做法。3. 避坑与排查五个真实翻车点3.1 填充溢出外壳描边被盖住现象电量接近 100% 时填充色的圆角盖住了外壳描边看起来描边断了一截。原因填充路径的圆角半径和外壳圆角半径不一致填充的弧线跑到描边外侧去了。解决填充圆角必须等于外壳圆角减内边距并且用setClipPath(shellPath)做裁剪兜底。只靠算半径不够因为浮点误差在极端值下仍可能溢出裁剪是最后一道保险。3.2 控件拉伸后比例全乱现象把控件从 200×60 拉到 400×60电池变扁正极凸起变成一条细线。原因几何参数用了固定像素值没有按高度归一化。解决所有尺寸都从height()推导宽度按比例算正极凸起的宽高都相对外壳尺寸。上面那张参数表就是为此设计的照抄即可。另外记得在sizeHint()里返回一个合理的最小尺寸避免布局把它压到 0。3.3 抗锯齿开了但边缘还是毛糙现象setRenderHint(Antialiasing)已经开了圆角处仍有锯齿。原因描边宽度是奇数像素且没有对齐半像素或者QPainter的坐标是整数导致圆角路径落在像素边界上。解决描边宽度用qMax(1.0, ...)保证至少 1 像素坐标尽量用qreal浮点必要时在绘制前painter.translate(0.5, 0.5)做半像素对齐。这个坑在低分屏上不明显高分屏缩放后特别突出。3.4 频繁 update 导致 CPU 飙高现象设备面板上电量每秒刷新CPU 占用比预期高不少。原因setValue无条件update()即使值没变也重绘。解决加值相等判断提前返回另外把paintEvent里不变的部分比如外壳路径缓存成成员变量只在resizeEvent里重算。如果还有呼吸动画动画只改透明度不要每帧重算几何。3.5 高 DPI 下图标模糊现象在 2 倍缩放的屏幕上充电闪电边缘发虚。原因闪电用固定像素坐标画没有考虑devicePixelRatio。解决闪电的坐标全部相对填充区尺寸计算不要写死像素如果用了位图图标换成QPainterPath矢量绘制或者提供2x资源。矢量绘制是最省事的闪电这种简单图形用路径画比贴图清晰得多。4. 进阶把电池控件做成可复用组件写到能跑只是第一步真正落地时你会希望这个控件能像普通 Qt 控件一样用——支持样式表设颜色、支持动画过渡、支持在 Designer 里拖。这里说两个我实际用下来收益最大的技巧。第一个是暴露Q_PROPERTY让颜色和值可以通过属性系统设置这样既能用setProperty在代码里改也能被QPropertyAnimation驱动。比如给value加一个动画电量变化时填充平滑过渡而不是跳变// 头文件里声明 Q_PROPERTY(int value READ value WRITE setValue) // 使用处值变化时走 300ms 动画 auto *anim new QPropertyAnimation(battery, value, this); anim-setDuration(300); anim-setStartValue(battery-value()); anim-setEndValue(newValue); anim-setEasingCurve(QEasingCurve::OutCubic); anim-start(QAbstractAnimation::DeleteWhenStopped);注意setValue里那个「值相等就返回」的判断要保留动画每帧都会调setValue值不同才会重绘逻辑不冲突。动画的缓动曲线用OutCubic比较自然线性动画看起来机械。第二个是重写sizeHint()和minimumSizeHint()给布局一个合理建议值。电池控件的最小尺寸建议不低于 60×24低于这个尺寸圆角和正极凸起都画不清楚。sizeHint返回 120×48 左右比较通用具体按你的面板密度调。验证方法很简单写一个测试窗口放三个电池控件分别设 15%、55%、95%再放一个滑块联动其中一个拖动滑块看填充是否平滑、颜色是否在阈值处正确切换、充电开关切换时闪电是否出现和消失。这个自测流程我每次改绘制代码都会走一遍比盯着代码看有效得多。从那以后我每次写自定义绘制控件都强制先把几何参数归一化成一张表、再动手写paintEvent因为翻车最多的从来不是绘制 API 用错而是尺寸关系没想清楚。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑