Qt-for-鸿蒙PC-水波纹进度条组件开发实战 - 详解
目录
项目概述


项目背景
水波纹进度条是一种视觉效果独特的进度展示组件,通过模拟水波纹的波动效果来展示进度信息。本项目基于Qt/QML框架,实现了矩形和圆形两种形状的水波纹进度条,为HarmonyOS应用提供生动、直观的进度展示方案。
组件特性
本项目实现了以下两种水波纹进度条:
- ✅ 矩形水波纹进度条:适用于方形容器,展示矩形区域的水波纹效果
- ✅ 圆形水波纹进度条:适用于圆形容器,展示圆形区域的水波纹效果
所有组件均支持:
- 自定义进度值(value、minimum、maximum)
- 自定义颜色(水波纹颜色、背景颜色)
- 动态水波纹动画效果
- 双波纹叠加效果
- 垂直渐变填充
- 百分比文字显示
技术栈选择
前端框架:Qt/QML
选择理由:
- Canvas API:QML的Canvas组件提供了强大的2D绘制能力,可以精确控制水波纹的绘制
- Timer机制:使用Timer实现水波纹的周期性动画更新
- 数学函数支持:JavaScript的Math.sin函数可以方便地生成正弦波形
- 性能优化:Canvas的裁剪机制可以优化绘制性能
核心技术点
- QtQuick 2.15:UI框架
- Canvas API:2D绘制
- Timer:动画更新机制
- Math.sin:正弦波生成
- LinearGradient:垂直渐变
组件设计
整体架构
水波纹进度条组件的架构设计:
Item (root)
├── Canvas
├── 背景绘制(矩形/圆形)
├── 裁剪区域设置
├── 渐变创建(垂直方向)
├── 第一层水波纹绘制
└── 第二层水波纹绘制(相位差)
└── 文字显示(百分比)
核心属性
property real value: 0 // 当前值
property real minimum: 0 // 最小值
property real maximum: 100 // 最大值
property real progress: ... // 计算得出的进度比例(反向:1 - (value-min)/(max-min))
property bool isCircle: true // 是否为圆形
property color waterColor // 水波纹颜色
property color backgroundColor // 背景颜色
property real backgroundOpacity // 背景透明度
// 水波纹参数
property real waveOffset: 0 // 波纹偏移量(动画)
property real waveAmplitude: 0 // 波纹振幅
property real waveFrequency: 0.038 // 波纹频率
核心功能实现
1. 进度值计算(反向)
水波纹进度条使用反向进度计算,值越大,水位越高:
property real progress: Math.max(0, Math.min(1, 1 - (value - minimum) / (maximum - minimum)))
说明:
- 当
value = minimum时,progress = 1,水位在底部(满) - 当
value = maximum时,progress = 0,水位在顶部(空) - 这与常规进度条相反,符合"水位上升"的视觉效果
2. 水波纹动画
使用Timer实现水波纹的周期性动画:
Timer {
id: waveTimer
interval: 80 // 每80ms更新一次
running: true
repeat: true
onTriggered: {
waveOffset += 0.6 // 增加偏移量
if (waveOffset > width / 2) {
waveOffset = 0 // 重置偏移量
}
}
}
关键技术点:
interval: 80:控制动画帧率,80ms约等于12.5fpswaveOffset += 0.6:每次增加的偏移量,控制波纹移动速度- 重置机制:当偏移量超过容器宽度的一半时重置,避免数值过大
3. 波纹振幅计算
波纹振幅根据容器高度动态计算:
onHeightChanged: {
waveAmplitude = height * 0.05 // 振幅为高度的5%
}
Component.onCompleted: {
waveAmplitude = height * 0.05
}
说明:
- 振幅设置为高度的5%,确保波纹效果明显但不过于夸张
- 在
onHeightChanged和Component.onCompleted中都设置,确保初始化正确
4. Canvas绘制实现
4.1 背景绘制
// 绘制背景
ctx.fillStyle = Qt.rgba(1, 1, 1, backgroundOpacity)
ctx.beginPath()
if (isCircle) {
// 圆形背景
var side = Math.min(width, height)
var centerX = width / 2
var centerY = height / 2
ctx.arc(centerX, centerY, side / 2, 0, 2 * Math.PI)
} else {
// 矩形背景
ctx.rect(0, 0, width, height)
}
ctx.fill()
4.2 裁剪区域设置
使用 clip()方法限制绘制区域:
ctx.save()
ctx.beginPath()
if (isCircle) {
var side = Math.min(width, height)
var centerX = width / 2
var centerY = height / 2
ctx.arc(centerX, centerY, side / 2, 0, 2 * Math.PI)
} else {
ctx.rect(0, 0, width, height)
}
ctx.clip() // 设置裁剪区域
说明:
ctx.save():保存当前绘图状态ctx.clip():设置裁剪区域,后续绘制只在此区域内可见ctx.restore():恢复绘图状态(在绘制完成后调用)
4.3 垂直渐变创建
创建两个不同透明度的垂直渐变:
// 第一个渐变(透明度较低)
var gradient1 = ctx.createLinearGradient(0, 0, 0, height)
gradient1.addColorStop(0.0, Qt.rgba(255/255, 66/255, 213/255, 0.4)) // 粉色,透明度0.4
gradient1.addColorStop(1.0, Qt.rgba(43/255, 74/255, 255/255, 0.4)) // 蓝色,透明度0.4
// 第二个渐变(透明度较高)
var gradient2 = ctx.createLinearGradient(0, 0, 0, height)
gradient2.addColorStop(0.0, Qt.rgba(255/255, 66/255, 213/255, 0.7)) // 粉色,透明度0.7
gradient2.addColorStop(1.0, Qt.rgba(43/255, 74/255, 255/255, 0.7)) // 蓝色,透明度0.7
说明:
- 渐变方向:从上到下(0, 0)到(0, height)
- 颜色:粉色到蓝色,与项目整体配色方案一致
- 透明度:两个渐变使用不同透明度,叠加后产生层次感
4.4 第一层水波纹绘制
使用正弦函数生成波纹路径:
ctx.fillStyle = gradient1
ctx.beginPath()
ctx.moveTo(0, height)
for (var x = 0; x <= width; x += 1) {
var y1 = waveAmplitude * Math.sin(waveFrequency * x + waveOffset) + waterLevel
// 边界处理
if (value === minimum) {
y1 = height // 最小值时,水位在底部
}
if (value === maximum) {
y1 = 0 // 最大值时,水位在顶部
}
ctx.lineTo(x, y1)
}
ctx.lineTo(width, height)
ctx.closePath()
ctx.fill()
关键技术点:
Math.sin(waveFrequency * x + waveOffset):生成正弦波形waveFrequency * x:控制波纹的密度(频率)waveOffset:控制波纹的相位(动画效果)waterLevel:水位基准线,根据progress计算- 边界处理:确保在极值时水位位置正确
4.5 第二层水波纹绘制(相位差)
第二层波纹使用相位差,产生叠加效果:
ctx.fillStyle = gradient2
ctx.beginPath()
ctx.moveTo(0, height)
for (var x2 = 0; x2 <= width; x2 += 1) {
var phaseOffset = waveOffset + (width / 2 * waveFrequency) // 相位差
var y2 = waveAmplitude * Math.sin(waveFrequency * x2 + phaseOffset) + waterLevel
// 边界处理(与第一层相同)
if (value === minimum) {
y2 = height
}
if (value === maximum) {
y2 = 0
}
ctx.lineTo(x2, y2)
}
ctx.lineTo(width, height)
ctx.closePath()
ctx.fill()
关键技术点:
phaseOffset = waveOffset + (width / 2 * waveFrequency):相位差为容器宽度的一半乘以频率- 两层波纹叠加产生更丰富的视觉效果
- 使用不同透明度的渐变,增强层次感
5. 文字显示
在Canvas上绘制百分比文字:
// 绘制文字
ctx.fillStyle = "white"
var fontSize = Math.max(12, width / 8) // 字体大小自适应
ctx.font = fontSize + "px Arial"
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText(Math.round(value) + "%", width / 2, height / 2)
说明:
- 字体大小根据容器尺寸自适应
- 文字居中显示
- 使用
Math.round()四舍五入显示整数百分比
数学原理
正弦波函数
水波纹使用正弦函数生成:
y = A * sin(ω * x + φ) + y₀
其中:
A:振幅(amplitude),控制波纹的高度ω:角频率(angular frequency),控制波纹的密度x:水平位置φ:相位(phase),控制波纹的偏移y₀:基准线(waterLevel),根据进度值计算
相位差计算
第二层波纹的相位差:
phaseOffset = waveOffset + (width / 2 * waveFrequency)
这确保了两层波纹在视觉上形成良好的叠加效果。
水位计算
水位基准线根据进度值计算:
var waterLevel = height * progress
其中 progress是反向计算的进度值(1 - (value-min)/(max-min))。
开发要点与技巧
1. 性能优化
裁剪机制:
ctx.save()
ctx.beginPath()
// ... 设置裁剪路径
ctx.clip()
// ... 绘制内容
ctx.restore()
使用裁剪机制可以避免绘制超出边界的内容,提升性能。
重绘触发:
onWidthChanged: {
if (width > 0 && height > 0) {
requestPaint()
}
}
onHeightChanged: {
if (width > 0 && height > 0) {
requestPaint()
}
}
onProgressChanged: {
if (canvas.width > 0 && canvas.height > 0) {
canvas.requestPaint()
}
}
onWaveOffsetChanged: {
if (canvas.width > 0 && canvas.height > 0) {
canvas.requestPaint()
}
}
只在尺寸有效时触发重绘,避免无效绘制。
2. 边界处理
在极值情况下确保水位位置正确:
if (value === minimum) {
y1 = height // 最小值时,水位在底部(满)
}
if (value === maximum) {
y1 = 0 // 最大值时,水位在顶部(空)
}
3. 动画参数调优
波纹频率:
property real waveFrequency: 0.038
频率值影响波纹的密度,值越大波纹越密集。
动画速度:
waveOffset += 0.6
每次增加的偏移量控制波纹移动速度,值越大移动越快。
更新间隔:
interval: 80 // 80ms,约12.5fps
更新间隔影响动画流畅度,需要在流畅度和性能之间平衡。
4. 渐变透明度控制
使用不同透明度的渐变叠加:
// 第一层:透明度0.4
gradient1.addColorStop(0.0, Qt.rgba(255/255, 66/255, 213/255, 0.4))
gradient1.addColorStop(1.0, Qt.rgba(43/255, 74/255, 255/255, 0.4))
// 第二层:透明度0.7
gradient2.addColorStop(0.0, Qt.rgba(255/255, 66/255, 213/255, 0.7))
gradient2.addColorStop(1.0, Qt.rgba(43/255, 74/255, 255/255, 0.7))
两层叠加后产生更丰富的视觉效果。
使用示例
基本使用
import QtQuick 2.15
Column {
spacing: 20
// 矩形水波纹进度条
WaterProgressBar {
width: 200
height: 200
value: 50
isCircle: false
waterColor: "#2B4AFF"
}
// 圆形水波纹进度条
WaterProgressBar {
width: 200
height: 200
value: 75
isCircle: true
waterColor: "#2B4AFF"
}
}
动态更新进度
Item {
property real progressValue: 0
Timer {
interval: 100
running: true
repeat: true
onTriggered: {
progressValue += 1
if (progressValue > 100) progressValue = 0
}
}
WaterProgressBar {
width: 200
height: 200
value: progressValue
isCircle: true
}
}
自定义颜色
WaterProgressBar {
width: 200
height: 200
value: 60
isCircle: true
waterColor: "#FF6B6B" // 自定义水波纹颜色
backgroundColor: "#FFFFFF" // 自定义背景颜色
backgroundOpacity: 0.3 // 自定义背景透明度
}
总结与展望
技术总结
本项目成功实现了矩形和圆形两种形状的水波纹进度条,展示了Qt/QML Canvas API的强大能力:
- 数学建模:使用正弦函数精确模拟水波纹效果
- 动画机制:通过Timer实现流畅的动画更新
- 视觉效果:双波纹叠加和渐变填充产生丰富的视觉层次
- 性能优化:裁剪机制和合理的重绘策略保证流畅运行
扩展方向
- 更多波纹层:可以添加更多波纹层,产生更复杂的效果
- 不同波形:可以使用其他数学函数(如余弦、复合波形)生成不同效果
- 交互效果:可以添加点击涟漪效果
- 3D效果:可以添加阴影和高光,模拟3D水面效果
最佳实践
- 参数调优:波纹频率、振幅、动画速度需要根据实际需求调优
- 性能考虑:合理设置Timer间隔,避免过度绘制
- 边界处理:确保在极值情况下正确显示
- 代码复用:矩形和圆形使用统一的绘制逻辑,通过
isCircle参数区分
相关资源
- 项目地址: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
- 正弦函数参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
浙公网安备 33010602011771号