Qt中paintEvent自绘电池进度条完全指南

📅 发布时间:2026/10/15 7:13:29
Qt中paintEvent自绘电池进度条完全指南
简介这是一份面向Qt初学者与进阶者的自定义控件实例通过重写paintEvent绘制电池进度条完整涵盖电量比例计算、状态颜色切换与动画驱动等关键知识点。资源压缩包共8个文件包含3个cpp源文件、2个头文件、1个pro工程文件、1个ui界面文件及1个user配置文件整体仅7KB代码精简便于阅读和二次开发。项目利用QPainter的fillRect绘制电池外壳与电量区域根据电量比例动态切换颜色高于80%显示正常色低于20%显示警示色其余区间使用过渡色同时借助QPropertyAnimation驱动电量值变化每次更新自动触发重绘形成平滑动画效果。示例将绘制逻辑、动画逻辑与颜色判断拆分为独立方法并采用缓存机制优化绘制性能这种模块化设计对学习自绘控件和属性动画很有参考价值。已有498人学习下载适合作为Qt图形界面编程的练手项目。1. 从一张贴图到一次绘制paintEvent 是电池进度条的唯一正解Qt 里要做一个电池进度条很多人第一反应是找一张 PNG 贴上去但图片一放到高分屏就发糊、颜色不好改、宽高比一变就变形。真正稳定可控的做法是走 paintEvent 这条手工绘制链路控件内部通过重绘函数把电池外壳、电量填充和百分比文字一次性画出来。这样做不依赖任何图片资源运行时可以随便换颜色、改圆角、切成充电状态在车载状态栏、设备电量页和桌面小组件里都是通用方案。这篇文章把坐标定义、绘制顺序、关键参数和常见坑一次讲透适合刚接 Qt 自绘控件的开发者也适合想把绘制逻辑做得更规整的熟手。整个控件的核心不是“画得像”而是让这套绘制逻辑可复现、可维护、可换肤。我选这个题目是因为它小而完整电池看着简单但里面同时有几何布局、颜色语义、文字排版和反走样细节。练手成本低做成组件后的复用面却宽画通这一套再去做信号强度、音量滑块、仪表盘都是同一套思路。2. 先定坐标再动笔paintEvent 落笔前必须想清楚的四件事paintEvent 本质上是在一个已经确定的坐标系里做二维绘制坐标原点是控件左上角x 向右y 向下默认单位是像素。电池进度条的所有图形都可以拆成三块电池外壳、电量填充区、百分比文字。这三块必须共用同一套几何参数否则就会出现“电量已经 100% 但填充条还离边框一大截”这种一眼假的画面。下面按我习惯的顺序把这四个问题定下来。2.1 电池壳体的几何模型主体、正极与圆角坐标推导电池图形和真实电池对应由主体和正极组成。主体占据控件宽度去掉正极之后的部分正极是右侧一个小凸起。我在做的时候会先固定一个基准比例而不是用绝对像素这样控件在布局里被拉伸时不会“散架”。参考参数如下参数取值示例说明主体占宽比例0.82剩余宽度给正极圆角半径高度 × 0.18保证不同尺寸下弧度一致正极宽度高度 × 0.18同样跟随高度缩放描边宽度1.5px固定像素保证边缘清晰有了这三个比例主体矩形和正极矩形可以直接从height()推出来。注意先算矩形再画路径不要让圆角半径比矩形短边还大否则QPainterPath::addRoundedRect会把半径截断短边上出现一条压扁的曲线看起来非常奇怪。我一般会加一句保护逻辑圆角半径取qMin(h * 0.18, 主体高度 / 2.0)。正极只需要画一个实心圆角矩形或者普通矩形不需要和主体使用同一半径因为正极很小圆角差别看不出来。但正极和主体之间要留出 2~3px 间隙否则壳子连成一片视觉上分不出哪部分是正极。2.2 电量填充的收缩规则边框、内边距与充满率电量填充必须是电池主体向内收缩一圈之后的小矩形。常见错误是直接用主体矩形填充结果电量到 100% 时直接盖住描边线整个电池糊成一团。我的做法是先确定描边宽度为 1.5px然后主体矩形整体向内收缩 2px得到填充区。由于绘制路径时描边是沿路径中心线向两边扩散的borderWidth占据的视觉区域大约一半在矩形外、一半在矩形内所以内缩距离取描边宽度 1就足够安全。填充区的高度则按“充满率”计算。充满率不是简单等于电量百分比因为电池底部通常保留一点空隙更像真实电池我习惯在底部留 3% 的空隙所以qreal fillHeight fillRect.height() * (m_value / 100.0 * 0.97); QRectF currentFillRect QRectF( fillRect.left(), fillRect.top() fillRect.height() - fillHeight, fillRect.width(), fillHeight );注释0.97就是底部保留的 3% 视觉空隙当电量是 0% 时fillHeight为 0填充区只剩一条 1px 的底线用来表示“有框架但没电”。如果产品经理希望 0% 时完全看不到填充就把这个底线也去掉。电量变化时只改高度、宽度固定对齐视觉上电池是从底部往上“长”的比从中间往外扩张自然得多。2.3 电量颜色映射与充电状态把业务语义翻译成 QColor颜色不能只用一种绿色否则低电量时紧张感出不来。我习惯按三段映射电量大于 40% 用绿色系列20% 到 40% 用橙色系列低于 20% 用红色系列。每个区间内再做线性插值让颜色跟随电量平滑变化而不是在 39% 到 40% 之间突然跳色。插值用QColor::green()这种通道值手工算太啰嗦直接用 HSV 插值更稳定绿色对应色相约 120 度橙色约 30 度红色约 0 度。把电量百分比映射到色相区间再用QColor::fromHsv转回QColor饱和度固定 255明度 190~230 之间浮动。充电状态一般要叠加一个“正在充电”的提示最省事的方式是画一个闪电符号在填充区右上角。但闪电符号依赖字体字形比较挑字体我后面会讲替代方案。充电时还可以把边框颜色变成淡绿色并且电量颜色固定在绿色区间不和低电量红色冲突。注意颜色语义要在控件内部集中定义不要散落在绘制代码里这样后续如果想做“省电模式变灰”的皮肤只需要替换一组颜色变量。2.4 电量文字排版用 QFontMetrics 的四个值做双重居中百分比文字是电池进度条里最容易歪的一点。直接drawText(rect, Qt::AlignCenter, text)看似居中实际上文字在不同系统的字体度量差异下会有 1~2px 的偏移尤其带百分号时小字号下百分号的基线会让整体看起来偏上。我习惯手动计算QFontMetrics fm(font); QString text QString::number(m_value) %; qreal textWidth fm.horizontalAdvance(text); qreal textHeight fm.ascent() fm.descent(); QRectF textRect( fillRect.center().x() - textWidth / 2.0, fillRect.center().y() - textHeight / 2.0, textWidth, textHeight ); painter.drawText(textRect, Qt::AlignCenter, text);注释ascent()是基线以上的高度descent()是基线以下的高度两者相加才是真正的文字高度用fm.height()也可以但不同字体下height()可能包含额外行距做像素级居中时不够精细。当电量低于 20% 时文字颜色要改成白色否则深红色背景上写深色字会读不出来。字号建议跟随高度缩放QFont用 pointSize 设置控件较小时给一个最小字号避免百分比数字挤掉填充区的可见面积。3. 最小可复用的 BatteryWidget属性、重绘触发与完整实现原理定了接下来就把它落成一个能直接拖进窗体的QWidget。控件对外只暴露setValue、setCharging和一组颜色设置接口。内部在paintEvent里根据当前属性完成全部绘制外部不关心绘图细节这就是自绘控件的正确封装方式。3.1 控件骨架属性接口、事件重写与一次 update头文件里只需要四个成员电量值、充电标记、边框颜色、填充颜色。公共接口要做参数校验电量值限定在 0~100负数按 0、大于 100 按 100 处理。每次属性变更后调用update()请求重绘而不是直接repaint()。update()是异步的Qt 会合并事件连续多次setValue最终只重绘一次性能开销小repaint()是同步强刷在主线程里频繁调用可能造成界面卡顿不建议在电量动画这种高频场景使用。#ifndef BATTERYWIDGET_H #define BATTERYWIDGET_H #include QWidget #include QColor class BatteryWidget : public QWidget { Q_OBJECT public: explicit BatteryWidget(QWidget *parent nullptr); // 对外接口设置电量百分比0-100自动触发重绘 void setValue(int value); // 设置充电状态为 true 时切换配色并显示闪电标记 void setCharging(bool charging); // 自定义颜色不设置时使用默认绿/橙/红三段式 void setCustomColors(const QColor border, const QColor fill); protected: void paintEvent(QPaintEvent *event) override; private: // 根据当前控件尺寸计算壳体矩形 QRectF bodyRect() const; QRectF fillRect() const; int m_value 80; bool m_charging false; QColor m_borderColor QColor(0x40, 0x40, 0x40); QColor m_customFillColor; // 未设置时按电量自动选择 }; #endif // BATTERYWIDGET_H逻辑说明bodyRect()和fillRect()都依赖控件当前尺寸所以放在const成员函数里由paintEvent统一调用。这样只要看一眼这两个函数就能确认壳体大小和填充区大小是否同步不会出现绘制代码里各算各的。接口里setCustomColors设计成“可选覆盖”传空值就退回到默认配色方便往后扩展成属性面板。3.2 paintEvent 完整实现路径构造、填充与描边顺序paintEvent的绘制顺序是先画电池主体描边只画路径不填充再画正极然后画填充区最后画文字。这个顺序不能反。如果先填充主体再描边外壳颜色会被填充区从内部盖住一部分1.5px 的描边视觉上只剩下半圈。下面是我实际在用的完整实现关键步骤都写在注释里void BatteryWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); const QRectF body bodyRect(); qreal radius qMin(body.height() * 0.18, body.height() / 2.0); // 1. 画电池主体只描边不填充 QPainterPath bodyPath; bodyPath.addRoundedRect(body, radius, radius); QPen borderPen(m_borderColor); borderPen.setWidthF(1.5); painter.setPen(borderPen); painter.setBrush(Qt::NoBrush); painter.drawPath(bodyPath); // 2. 画正极右侧小凸起同样只描边并填充 qreal capWidth body.height() * 0.18; QRectF capRect(body.right() 1.5, body.top() body.height() * 0.3, capWidth, body.height() * 0.4); painter.setPen(borderPen); painter.setBrush(m_borderColor); painter.drawRoundedRect(capRect, 1.5, 1.5); // 3. 画电量填充区充满率底部预留 3% const QRectF fillArea fillRect(); qreal fillHeight fillArea.height() * (m_value / 100.0 * 0.97); QRectF currentFill(fillArea.left(), fillArea.top() fillArea.height() - fillHeight, fillArea.width(), fillHeight); painter.setPen(Qt::NoPen); painter.setBrush(QColor(0x34, 0xc7, 0x59)); painter.drawRoundedRect(currentFill, 2.0, 2.0); // 4. 画百分比文字低电量时文字用白色 if (m_value 20) { painter.setPen(Qt::white); } else { painter.setPen(QColor(0x20, 0x20, 0x20)); } QFont font painter.font(); font.setPointSize(qMax(8.0, body.height() * 0.22)); painter.setFont(font); QString text QString::number(m_value) %; painter.drawText(currentFill, Qt::AlignCenter, text); }逻辑说明第 3 步里fillRect()返回的是主体矩形向内收缩 2px 后的区域所以右边界和底部已经预留了间距描边线不会被填充覆盖。第 4 步把文字画在currentFill内部电量低时填充区面积小文字自动被限制在可见区域里不需要额外裁剪路径。QPainterPath在这里的作用是让外壳边框和内部填充共用同一套圆角定义避免用drawRoundedRect画外壳、用普通矩形画填充导致圆角处留出三角空隙。参数说明QPen的setWidthF(1.5)是为了在反走样下让 1px 细线看起来更锐利。整数 1 在反走样里会出现半透明边缘导致壳子发虚1.5 在大多数屏幕上能稳定画出清晰的灰线。正极的圆角用固定 1.5因为正极小太大圆角会把它画得像个药丸。文字字号body.height() * 0.22乘出来的是像素为了让它在不同布局高度下保持合理比例控件高度 40px 时字号约为 8.8px可读性够用。3.3 外部驱动用 QTimer 模拟电量变化观察重绘节奏这个控件本身不发电量数据需要外部调用setValue。最简单的方式是在宿主窗口里挂一个QTimerQTimer *timer new QTimer(this); BatteryWidget *battery new BatteryWidget(this); connect(timer, QTimer::timeout, this, []() { static int v 100; v - 1; if (v 0) v 100; battery-setValue(v); }); timer-start(1000);逻辑说明定时器每秒减少 1% 电量绕回 100 以后继续循环用来观察三种颜色区间的切换是否顺畅。如果你只想看充电状态就改为每 2 秒调用一次setCharging(true / false)。update()是异步合并的所以 1 秒一次的频率几乎不占 CPU但如果你把定时器改成 10ms 一次就会暴露另一个问题绘制本身不慢但宿主窗口的布局会被频繁触发的重绘拖慢。电量动画的正确做法后面会讲这里只是验证控件是否能正常工作。4. 逼真感调参清单渐变、高光、阴影与充电闪烁的克制使用画电池不是越复杂越好看。绘制代码每多一个效果重绘开销和出错概率就上去了。我见过把电池画成三层渐变、两道高光、投影外加呼吸灯效果的结果在嵌入式设备上电量更新时掉帧明显。正确的做法是先画一个干净清晰的指标然后在需要强调的地方加一层效果保留一层调试用的开关。4.1 填充区渐变什么时候值得牺牲两毫秒绘制时间平涂填充在色彩单一的大屏上容易显得死板加上线性渐变后立体感立刻出来。常见的做法是在填充区上做一个从上到下的垂直渐变上端颜色比下端亮 15%~20%模拟光线从顶部照下来的感觉。实现用的是QLinearGradientQLinearGradient gradient(fillRect.topLeft(), fillRect.bottomLeft()); gradient.setColorAt(0.0, fillColor.lighter(125)); gradient.setColorAt(0.4, fillColor); gradient.setColorAt(1.0, fillColor.darker(115)); painter.setBrush(gradient);逻辑说明lighter(125)表示亮度变为原来的 125%darker(115)是变暗 15%。中间 40% 处保持原色是为了让渐变过渡自然不会出现顶部一条亮线底部一块黑斑。代价是每个绘制周期都要构造一个渐变对象但一个电池控件的面积通常不超过 200×60px构造耗时在微秒级可以忽略。真正影响性能的是后面讲的带 alpha 通道的半透明叠加那会让整个窗口的合成路径变长。4.2 高光与阴影用 1px 半透明路径模拟玻璃质感高光其实是画在电池主体内侧的一条细线。它不参与电量填充始终固定在壳体上部颜色用白色 20% 透明度宽度 1px。具体写法是在电池主体矩形上再取一个缩小的矩形只画顶部那一条线段QPainterPath highlightPath; highlightPath.moveTo(body.left() radius, body.top() 1.0); highlightPath.lineTo(body.right() - radius, body.top() 1.0); painter.setPen(QPen(QColor(255, 255, 255, 50), 1.0)); painter.drawPath(highlightPath);逻辑说明这条线会同时被主体描边和填充区半压住因为填充区从 y 坐标比body.top()大两、三像素的地方开始所以只有顶部一条白色细线露出来形成“屏幕盖板反光”的错觉。注意如果控件高度小于 20px这条高光会直接和填充区叠成一层灰白色此时应该关掉高光。我一般会在头文件里留一个bool m_showHighlight由外部在尺寸过小时关闭而不是靠 if 硬编码尺寸这样逻辑更清晰。4.3 充电闪烁与呼吸动画用 QVariantAnimation 而不是高频 update充电动画不能做成整个电池描边闪烁那种效果像警报灯不像在充电。常见的做法是让电量填充分段跳动每 500ms 电量值加 1模拟充电进度同时正极变为绿色。如果想让呼吸效果更细腻就用QVariantAnimation驱动一个透明度变量在 0.3~1.0 之间来回变化让填充区颜色透明度随之呼吸QVariantAnimation *breath new QVariantAnimation(this); breath-setStartValue(0.3); breath-setEndValue(1.0); breath-setDuration(1200); breath-setEasingCurve(QEasingCurve::InOutSine); connect(breath, QVariantAnimation::valueChanged, this, [](const QVariant v) { m_chargAlpha v.toReal(); update(); }); breath-start();逻辑说明QVariantAnimation会按 easing 曲线生成中间值并在每次变化时触发update()。1200ms 的周期做一次“亮-暗”循环视觉上比 10ms 定时器手动累加要流畅得多代码也更短。用完之后记得在setCharging(false)时停掉动画并恢复 alpha 为 1.0否则取消充电后电池还在“呼吸”用户会以为是闪烁 Bug。4.4 充电闪电标记绕开字体依赖的手绘路径方案drawText画 “⚡” 在 Windows 上大概率显示成方块因为默认中文字体里没有这个字形。我的替代方案是直接画一个简化闪电用五六个点连成一条折线。它不需要字体回退而且颜色能跟随充电状态变化。最省事的方式是定义一组相对于填充区的比例坐标用QPainterPath连接并填充QPainterPath bolt; qreal cx fillRect.center().x(); qreal cy fillRect.center().y(); bolt.moveTo(cx 4.0, cy - 8.0); bolt.lineTo(cx - 4.0, cy); bolt.lineTo(cx 1.0, cy); bolt.lineTo(cx - 4.0, cy 8.0); bolt.lineTo(cx 5.0, cy); bolt.lineTo(cx - 1.0, cy); bolt.closeSubpath(); painter.setPen(Qt::NoPen); painter.setBrush(QColor(255, 255, 255, 220)); painter.drawPath(bolt);逻辑说明折线的坐标是以填充区中心为基准的所以闪电图会随着电量变化上下移动。如果想让闪电固定在电池右上角就把坐标改成相对电池主体左上角来算。这个方案的缺点是闪电形状需要反复调点但调完之后不会再受系统字体影响值得这样做。5. 电池进度条踩坑手册五个最常见的翻车现场这部分是我做自绘控件两年多攒下来的排查记录。现象、原因、解决办法按条目写开每一个都是我或者带过的同事实际遇到过的有些问题在你自己的机器上不一定立刻出现但换一台机器、换一套系统字体、换一个缩放比例就冒出来了。5.1 控件被拉成“胖电池”固定宽高比与最小尺寸的交锋现象电池放进QVBoxLayout后布局自动把宽度拉满电池变成一只扁平胶囊正极比例失调。原因QWidget默认的sizeHint()为空布局系统不知道这个控件想要什么尺寸于是按“能伸展就伸展”的策略处理宽度变成和窗体一样宽。解决重写sizeHint()返回一个固定比例比如宽度是高度的 2.6 倍。再在构造函数里设置setMinimumSize(90, 32)防止被压缩到看不清。如果布局里希望它跟随窗口横向缩放那就不要固定宽度而是用setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)让高度固定、宽度自适应再在resizeEvent里限制最大宽高比。否则电池会变成整排横条完全失去电池的样子。5.2 圆角被“啃”出毛边路径闭合与描边宽度的三角关系现象电池主体左下角和右下角出现缺口或者一条肉眼可见的细白线电量填充时四角比外壳更尖。原因外壳路径用addRoundedRect生成填充区用的是普通矩形二者内缩 2px 但圆角半径不同外壳圆角大、填充圆角小视觉上就会出现一圈不齐的描边。另一个原因是QPen的默认capStyle是SquareCap或FlatCap线条端点可能超出路径边界在圆形转角处留下一小段突起。解决填充区也改用drawRoundedRect圆角半径固定为 2px。同时把钢笔的capStyle和joinStyle显式设置为Qt::RoundCap和Qt::RoundJoin。joinStyle影响折线连接处是锐利尖角还是圆滑拐角在圆角路径里不设置会导致两个半径相交处出现尖刺。5.3 电量的“100%”数字炸出豆腐块中文字体回退与 pointSize 单位陷阱现象同一套代码在开发机上正常打包到目标机器后“100%”里的百分号变成方框或者数字明显偏大偏小。原因目标机器上没有代码里指定的字体Qt 回退到一个不含百分号字形的字体或者字体选的是固定位图字体不支持缩放。解决不要直接依赖默认字体在构造函数里指定一个常见字体族QFont f font(); f.setFamily(QStringLiteral(Microsoft YaHei)); f.setPointSizeF(9.0); setFont(f);这里我用setPointSizeF而不是setPixelSize因为 pointSize 会跟随系统 DPI 缩放像素大小是固定的。但如果控件高度很小pointSize 固定值在小屏上仍然偏大更稳妥的做法是在paintEvent里根据body.height()动态计算像素字号。另外百分号字形在绝大多数中文字体里都存在真正缺字形的是 “⚡”“⌀” 这种符号这也是我上一章改成手绘闪电的原因。5.4 update() 调了但画面纹丝不动窗口未显示与事件阻断现象在主窗口构造函数里连续调用setValue(30)和setValue(60)界面启动后一次更新都不出现直接停在初始值。原因控件还没被显示时update()只是给事件系统打了一个标记主窗口还没进入事件循环就不会派发绘制事件。如果后续界面也没有任何事件触发整个绘制就可能被推迟到无法察觉。解决这不是控件代码的问题是调用时机问题。确保setValue在窗口show()之后调用或者开始时先强制读一次初始值。我习惯在控件内部维护m_value启动后第一条setValue就算在窗口显示前调用等窗口显示时paintEvent第一次执行也会读到正确的m_value所以这个问题通常不会真正影响显示除非你在paintEvent里用了某个外部临时变量。5.5 高 DPI 屏上文字发糊devicePixelRatio 接管绘制管线现象在缩放 150% 的屏幕上电池壳子边缘发虚数字文字像隔着一层毛玻璃。原因Qt 默认启用高 DPI 缩放后paintEvent里的坐标仍按逻辑像素计算绘制结果会被放大到物理像素。反走样依赖整数倍率在非整数倍缩放下边缘会不均匀。另一个原因是没有处理devicePixelRatio把QImage作为离屏缓冲时尺寸算错。解决自绘控件本身不需要额外处理因为QPainter绘制到QWidget上时会自动完成 DPI 转换。但如果做了离屏渲染就必须手动乘devicePixelRatioqreal dpr devicePixelRatioF(); QPixmap pixmap(size() * dpr); pixmap.setDevicePixelRatio(dpr);逻辑说明size() * dpr让离屏缓冲覆盖物理像素setDevicePixelRatio告诉绘制设备每个逻辑点对应几个物理像素。如果漏掉这两个操作离屏渲染图导出后只有实际分辨率的一半在 150% 屏上导出的是 200×60 而不是 300×90。此坑在写测试用例导出截图时特别容易踩。6. 画完怎么验证与复用离屏渲染、像素比对与静态绘制函数电池控件画完不能只靠肉眼确认尤其当它要嵌进状态栏和嵌入式 UI 里时截图确认成本太高。我习惯把控件渲染到离屏QImage上再对关键像素做抽样检查。下面这段代码可以在单元测试里跑BatteryWidget battery; battery.resize(140, 50); battery.setValue(80); QImage image(battery.size(), QImage::Format_ARGB32); image.fill(Qt::white); battery.render(image); QColor sampleColor image.pixelColor(QPoint(70, 10)); qDebug() sampleColor.name(); image.save(battery_export.png);逻辑说明render会把控件的paintEvent结果画到任意QPaintDevice上所以QImage、QPixmap、QPrinter都能用。抽样点选在电池主体内部上方位置如果它是填充绿色系的颜色说明填充区几何计算没问题。如果你还想校验文字位置可以对文字中心上下两行分别采样确认一个在填充色上、一个在背景色上。这比截图再人工比对快得多也适合做成 CI 断言。复用上我做了一个提炼动作把paintEvent里从几何计算到填充绘制的逻辑抽到一个静态函数里。这样电池绘制不只属于BatteryWidget这个控件任何拿到QPainter的地方都能画。例如我要给打印报表加一个电量图例只需要把打印机的QPainter传进去不用为了打印再包一个控件实例。这个函数签名大致长这样static void paintBattery(QPainter *painter, const QRectF rect, int value, bool charging);函数内部先基于rect计算几何再执行绘制。paintEvent只是把这个静态函数包了一层。好处是单元测试可以直接传一个 100×40 的QRectF跑绘制不用实例化控件也避免了控件尺寸对结果的影响。我每次画完一个自绘控件都会做一次这个提炼其实把验证和复用一起解决了比单独整理文档管用。最后再说一个习惯电量数值刷新时我总会在setValue里加一句拦截值没变直接返回避免动画循环里每帧都触发无意义重绘。这个细节看着小放桌面小组件十几毫秒的刷新循环里就是 CPU 占用率 0% 和 3% 的区别。画电池这个套路跑通之后你再去画任何带几何比例的自绘控件都会先想“坐标系怎么定、参数怎么暴露、重绘怎么触发”这三个问题路就顺了。希望帮到你。本文还有配套的精品资源点击获取