Qt-for-鸿蒙PC 圆形进度条组件开发实战
圆形进度条是一种常见的进度展示组件,适用于需要节省空间或强调视觉效果的场景。本项目基于Qt/QML框架,实现了多种样式的圆形进度条,包括Donut(圆环)、Pie(饼图)和Line(线条)三种样式,为HarmonyOS应用提供灵活的圆形进度展示方案。property string textFormat: "%p%" // 默认:显示百分比// 示例:textFormat: "%v/%m" // 显
·
项目概述


项目背景
圆形进度条是一种常见的进度展示组件,适用于需要节省空间或强调视觉效果的场景。本项目基于Qt/QML框架,实现了多种样式的圆形进度条,包括Donut(圆环)、Pie(饼图)和Line(线条)三种样式,为HarmonyOS应用提供灵活的圆形进度展示方案。
组件特性
本项目实现了以下三种圆形进度条样式:
- ✅ Donut样式(圆环):带有内圆的圆环进度条,中间可显示文字
- ✅ Pie样式(饼图):实心圆形进度条,类似饼图
- ✅ Line样式(线条):细线条圆形进度条,简洁风格
所有样式均支持:
- 自定义进度值(value、minimum、maximum)
- 自定义颜色(进度色、背景色、文字色)
- 自定义起始角度
- 可选的文字显示
- 可自定义的文字格式
技术栈选择
前端框架:Qt/QML
选择理由:
- Canvas API:QML的Canvas组件提供了强大的2D绘制能力,可以精确绘制圆弧和扇形
- 数学计算:JavaScript的Math函数可以方便地进行角度和弧度转换
- 灵活配置:通过属性控制不同样式,代码复用性高
- 性能优化:Canvas绘制性能优异,适合频繁更新
核心技术点
- QtQuick 2.15:UI框架
- Canvas API:2D绘制,特别是
arc()方法 - Math.PI:角度和弧度转换
- 属性绑定:响应式更新
组件设计
整体架构
圆形进度条组件的架构设计:
Item (root)
└── Canvas
├── 背景圆绘制
├── 进度圆弧/扇形绘制
├── 内圆绘制(Donut样式)
└── 文字绘制(可选)
核心属性
property real value: 0 // 当前值
property real minimum: 0 // 最小值
property real maximum: 100 // 最大值
property real progress: ... // 计算得出的进度比例(0-1)
property int barStyle: 0 // 样式:0=Donut, 1=Pie, 2=Line
property real startAngle: -90 // 起始角度(度),-90表示从顶部开始
property color progressColor // 进度颜色
property color backgroundColor // 背景颜色
property color textColor // 文字颜色
property bool showText: true // 是否显示文字
property string textFormat: "%p%" // 文字格式:%p=百分比, %v=值, %m=最大值
// Donut样式专用
property real outlineWidth: 1 // 轮廓宽度
property real dataWidth: 8 // 线条宽度(Line样式)
核心功能实现
1. 进度值计算
使用标准的进度计算方式:
property real progress: Math.max(0, Math.min(1, (value - minimum) / (maximum - minimum)))
确保进度值始终在0-1范围内。
2. Canvas绘制实现
2.1 初始化与清理
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
if (width <= 0 || height <= 0) return
var ctx = getContext("2d")
if (!ctx) return
ctx.clearRect(0, 0, width, height) // 清除画布
// ... 绘制内容
}
}
说明:
- 检查尺寸有效性,避免无效绘制
- 清除画布,确保每次绘制都是干净的
2.2 计算中心点和半径
var centerX = width / 2
var centerY = height / 2
var outerRadius = Math.min(width, height) / 2 - 1 // 外圆半径,留1px边距
var innerRadius = barStyle === 0 ? outerRadius * 0.75 : 0 // 内圆半径(仅Donut样式)
关键技术点:
- 使用
Math.min(width, height)确保圆形适配不同宽高比 - Donut样式的内圆半径为外圆的75%
- 其他样式不需要内圆(
innerRadius = 0)
2.3 背景圆绘制
根据样式绘制不同的背景:
// Donut和Pie样式:填充背景圆
if (barStyle === 0 || barStyle === 1) {
ctx.beginPath()
ctx.arc(centerX, centerY, outerRadius, 0, 2 * Math.PI)
ctx.fillStyle = backgroundColor
ctx.fill()
}
// Line样式:描边背景圆
else if (barStyle === 2) {
ctx.beginPath()
ctx.arc(centerX, centerY, outerRadius - outlineWidth / 2, 0, 2 * Math.PI)
ctx.strokeStyle = backgroundColor
ctx.lineWidth = outlineWidth
ctx.stroke()
}
说明:
- Donut和Pie样式使用
fill()填充 - Line样式使用
stroke()描边 - Line样式的半径需要减去
outlineWidth / 2,确保线条居中
2.4 进度绘制
根据进度值和样式绘制进度:
if (progress > 0) {
var angle = progress * 360 // 进度角度(度)
var startAngleRad = startAngle * Math.PI / 180 // 起始角度(弧度)
var endAngleRad = startAngleRad - angle * Math.PI / 180 // 结束角度(弧度)
ctx.beginPath()
if (barStyle === 0) {
// Donut样式:绘制圆环
// ...
} else if (barStyle === 1) {
// Pie样式:绘制扇形
// ...
} else if (barStyle === 2) {
// Line样式:绘制圆弧
// ...
}
}
关键技术点:
- 角度计算:
angle = progress * 360 - 弧度转换:
角度 * Math.PI / 180 - 注意:Canvas的
arc()方法使用弧度,且角度方向为顺时针
2.5 Donut样式实现
if (barStyle === 0) {
// 外圆弧
ctx.arc(centerX, centerY, outerRadius, startAngleRad, endAngleRad, true)
// 内圆弧(反向)
ctx.arc(centerX, centerY, innerRadius, endAngleRad, startAngleRad, false)
ctx.closePath()
ctx.fillStyle = progressColor
ctx.fill()
}
关键技术点:
- 使用两个
arc()调用绘制圆环 - 第一个
arc():外圆弧,true表示逆时针 - 第二个
arc():内圆弧,false表示顺时针,形成闭合路径 closePath():闭合路径,形成圆环
2.6 Pie样式实现
else if (barStyle === 1) {
// 移动到圆心
ctx.moveTo(centerX, centerY)
// 绘制外圆弧
ctx.arc(centerX, centerY, outerRadius, startAngleRad, endAngleRad, true)
// 回到圆心
ctx.lineTo(centerX, centerY)
ctx.closePath()
ctx.fillStyle = progressColor
ctx.fill()
}
关键技术点:
moveTo(centerX, centerY):移动到圆心arc():绘制外圆弧lineTo(centerX, centerY):回到圆心,形成扇形closePath():闭合路径
2.7 Line样式实现
else if (barStyle === 2) {
// 绘制圆弧
ctx.arc(centerX, centerY, outerRadius - outlineWidth / 2, startAngleRad, endAngleRad, true)
ctx.strokeStyle = progressColor
ctx.lineWidth = dataWidth
ctx.stroke()
}
关键技术点:
- 使用
stroke()描边,而不是fill()填充 - 半径减去
outlineWidth / 2,确保线条居中 lineWidth控制线条粗细
2.8 内圆绘制(Donut样式)
// 绘制内圆(Donut样式)
if (barStyle === 0 && innerRadius > 0) {
ctx.beginPath()
ctx.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI)
ctx.fillStyle = "#FFFFFF" // 白色内圆
ctx.fill()
}
说明:
- 只在Donut样式且内圆半径大于0时绘制
- 内圆通常使用白色,与背景形成对比
2.9 文字绘制
// 绘制文字
if (showText && (barStyle === 0 || barStyle === 1)) {
var text = textFormat.replace("%p", Math.round(progress * 100)) // 百分比
text = text.replace("%v", Math.round(value)) // 值
text = text.replace("%m", Math.round(maximum)) // 最大值
ctx.fillStyle = textColor
ctx.font = (innerRadius * 0.3) + "px Arial" // 字体大小根据内圆半径计算
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText(text, centerX, centerY)
}
关键技术点:
- 文字格式支持占位符:
%p(百分比)、%v(值)、%m(最大值) - 字体大小根据内圆半径自适应
textAlign和textBaseline设置为居中
3. 响应式更新
组件响应多个属性的变化:
onWidthChanged: {
if (width > 0 && height > 0) {
requestPaint()
}
}
onHeightChanged: {
if (width > 0 && height > 0) {
requestPaint()
}
}
onProgressChanged: {
if (canvas.width > 0 && canvas.height > 0) {
canvas.requestPaint()
}
}
onBarStyleChanged: {
if (canvas.width > 0 && canvas.height > 0) {
canvas.requestPaint()
}
}
onValueChanged: {
if (canvas.width > 0 && canvas.height > 0) {
canvas.requestPaint()
}
}
说明:
- 只在尺寸有效时触发重绘
- 响应所有影响显示效果的属性变化
数学原理
角度与弧度转换
Canvas的arc()方法使用弧度,而通常角度使用度:
弧度 = 角度 × π / 180
角度 = 弧度 × 180 / π
圆弧绘制
arc()方法的参数:
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
x, y:圆心坐标radius:半径startAngle:起始角度(弧度)endAngle:结束角度(弧度)anticlockwise:是否逆时针(true=逆时针,false=顺时针)
进度角度计算
进度角度 = 进度比例 × 360°
结束角度 = 起始角度 - 进度角度
注意:使用减法是因为通常进度条从顶部(-90°)开始,顺时针方向绘制。
开发要点与技巧
1. 样式切换
通过barStyle属性切换样式:
property int barStyle: 0 // 0=Donut, 1=Pie, 2=Line
在绘制逻辑中使用条件判断:
if (barStyle === 0) {
// Donut样式逻辑
} else if (barStyle === 1) {
// Pie样式逻辑
} else if (barStyle === 2) {
// Line样式逻辑
}
2. 起始角度设置
默认从顶部开始(-90°):
property real startAngle: -90 // 顶部开始
可以自定义起始角度:
RoundProgressBar {
startAngle: 0 // 从右侧开始
// 或
startAngle: 90 // 从底部开始
// 或
startAngle: -90 // 从顶部开始(默认)
}
3. 文字格式自定义
支持多种占位符:
property string textFormat: "%p%" // 默认:显示百分比
// 示例:
textFormat: "%v/%m" // 显示:50/100
textFormat: "%p%" // 显示:50%
textFormat: "%v" // 显示:50
4. 性能优化
条件绘制:
if (progress > 0) {
// 只在有进度时绘制
}
尺寸检查:
if (width <= 0 || height <= 0) return
避免无效重绘:
if (canvas.width > 0 && canvas.height > 0) {
canvas.requestPaint()
}
5. 颜色自定义
支持自定义所有颜色:
RoundProgressBar {
progressColor: "#2B4AFF" // 进度颜色(蓝色)
backgroundColor: "#E0E0E0" // 背景颜色(浅灰)
textColor: "#333333" // 文字颜色(深灰)
}
使用示例
基本使用
import QtQuick 2.15
Column {
spacing: 20
// Donut样式(圆环)
RoundProgressBar {
width: 200
height: 200
value: 75
barStyle: 0 // Donut
progressColor: "#2B4AFF"
}
// Pie样式(饼图)
RoundProgressBar {
width: 200
height: 200
value: 50
barStyle: 1 // Pie
progressColor: "#FF42D5"
}
// Line样式(线条)
RoundProgressBar {
width: 200
height: 200
value: 25
barStyle: 2 // Line
progressColor: "#32CD33"
}
}
动态更新进度
Item {
property real progressValue: 0
Timer {
interval: 100
running: true
repeat: true
onTriggered: {
progressValue += 1
if (progressValue > 100) progressValue = 0
}
}
RoundProgressBar {
width: 200
height: 200
value: progressValue
barStyle: 0
}
}
自定义文字格式
RoundProgressBar {
width: 200
height: 200
value: 60
barStyle: 0
textFormat: "%v/%m" // 显示:60/100
showText: true
textColor: "#333333"
}
自定义起始角度
RoundProgressBar {
width: 200
height: 200
value: 50
barStyle: 0
startAngle: 0 // 从右侧开始
}
总结与展望
技术总结
本项目成功实现了三种样式的圆形进度条,展示了Qt/QML Canvas API的强大能力:
- 多样式支持:通过属性控制实现Donut、Pie、Line三种样式
- 数学计算:精确的角度和弧度转换,确保绘制准确
- 灵活配置:支持自定义颜色、角度、文字格式等
- 性能优化:合理的重绘机制,保证流畅运行
扩展方向
- 动画效果:可以添加进度变化的动画过渡
- 渐变填充:可以使用渐变填充进度部分
- 更多样式:可以添加更多样式,如分段式、多层式等
- 交互功能:可以添加点击交互,允许用户手动设置进度
最佳实践
- 统一接口:所有样式使用统一的属性接口,便于维护
- 数学精确:确保角度和弧度转换的准确性
- 性能考虑:合理触发重绘,避免过度绘制
- 代码复用:通过条件判断实现样式切换,减少代码重复
相关资源
- 项目地址:https://gitcode.com/szkygc/HarmonyOs_PC-PGC/tree/main/progress
- Qt官方文档:https://doc.qt.io/qt-5/qtquick-index.html
- QML Canvas文档:https://doc.qt.io/qt-5/qml-qtquick-canvas.html
- Canvas arc方法:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/arc
更多推荐



所有评论(0)