QML ProgressBar 自定义样式:自绘组件、Material风格

默认进度条能用,但放进自己的界面里总觉得差点意思。这篇讲三条改样式的路子:完全不用控件、自己用 Rectangle 画;借用官方 Material 组件做细条;以及任务进度未知时的"不确定动画"。

  • 矩形自定义 --- 纯 Rectangle 自绘,胶囊外观 + 居中百分比,拖滑块看进度平滑跟随
  • Material 风格 --- 用官方 ProgressBarImpl 做细条进度,配合开关切换不确定模式
  • 不确定动画 --- 进度未知时用条纹滚动动画,表示"正在忙"

Demo 1:矩形自定义

不依赖 ProgressBar 控件,用两个 Rectangle 就把进度条画出来了:一个做底,一个做填充,中间叠一行百分比文字。拖动下面的滑块,填充跟着平滑动画过去。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        Rectangle {
            id: control
            Layout.fillWidth: true
            height: 24

            property int minimum: 0
            property int maximum: 100
            property int value: slider.value

            border.color: "#ccc"
            radius: height / 2
            clip: true

            Rectangle {
                width: ((control.width * (control.value - control.minimum)) / (control.maximum - control.minimum) - 6)
                height: parent.height
                Behavior on width { SmoothedAnimation { velocity: 1200 } }

                anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: 3 }
                radius: height / 2
                color: "#ff9800"
            }

            Text {
                anchors.centerIn: control
                color: "#333"
                font.bold: true
                text: Math.floor((control.value - control.minimum) / (control.maximum - control.minimum) * 100) + '%'
            }
        }

        Slider {
            id: slider
            Layout.fillWidth: true
            from: 0
            to: 100
            value: 30
            focusPolicy: Qt.NoFocus
        }

        Text {
            text: "进度值: " + control.value
            font.pointSize: 11
            color: "#666"
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

外层 Rectangle 当容器,自己定义 minimum / maximum / value 三个属性------是的,没用到 ProgressBar,属性名是自己起的,只是碰巧和官方一致,方便理解。

填充宽度是典型的进度换算:

qml 复制代码
width: (control.width * (control.value - control.minimum)) / (control.maximum - control.minimum) - 6

先算"当前值在范围内占了几成",乘上总宽度就是填充宽度。减去的 6 是给左右各留 3 的边距(配合 anchors.margins: 3),让填充不会贴死边框。

radius: height / 2 是胶囊形:高度 24,圆角半径 12,正好半个高,两头就是半圆。进度条整体比默认样式圆润。

百分比文字直接叠在进度条正中。文字用的是深色,因为橙色的填充在大部分进度下都够亮,深色字反而清楚。

拖动滑块时填充的"追着跑"效果来自 SmoothedAnimation:它和上一篇的 NumberAnimation 一样是给 width 变化加动画,区别是它按固定速度追目标值,拖得快就跟得紧,拖得慢就慢慢滑过去,手感更自然。

Demo 2:Material 风格

Material 风格不用自己画圆角矩形,官方提供了 ProgressBarImpl 这个内部组件,配合主题色,几行代码就是一条带涟漪感的细进度条。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Controls.Material.impl
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 20

        // ... 省略标题组件 TitleSeparator ...

        ProgressBar {
            id: control
            Layout.fillWidth: true
            value: 0.6

            contentItem: ProgressBarImpl {
                implicitHeight: 4

                scale: control.mirrored ? -1 : 1
                color: control.Material.accentColor
                progress: control.position
                indeterminate: control.visible && control.indeterminate
            }

            background: Rectangle {
                y: (control.height - height) / 2
                implicitHeight: 4
                color: Qt.rgba(control.Material.accentColor.r, control.Material.accentColor.g, control.Material.accentColor.b, 0.25)
            }
        }

        Switch {
            text: "开启不确定模式"
            font.pointSize: 11
            checked: control.indeterminate
            onClicked: control.indeterminate = !control.indeterminate
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

ProgressBarImpl 是 Qt 官方 Material 风格内部用的进度填充组件,把它塞进 contentItem,等于直接借用了官方画好的那层填充:

  • colorcontrol.Material.accentColor,跟着主题强调色走;
  • progresscontrol.position(就是 value 换算后的 0~1 位置);
  • indeterminate 透传控件的不确定状态。

background 用了一条极细的 4 高矩形,颜色是主题色的低透明度版(Qt.rgba 把 alpha 压到 0.25),配合 y 居中,形成"底浅条、上深条"的两层结构。这就是 Material 进度条的标志性外观。

底下的 Switch 用来切换 indeterminate。打开后,细条会变成官方的循环滑动动画------这是 ProgressBarImpl 内置的能力,一行动画代码都不用写。下一节的条纹动画就是自己实现这种"不确定"效果。

Demo 3:不确定动画

不知道任务还要多久时(比如"正在加载"),显示具体百分比反而尴尬。这个 demo 用一张条纹蒙版图,在进度条上循环滚动,做出经典的不确定动画。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Controls.impl
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        ProgressBar {
            id: control
            Layout.fillWidth: true
            height: 24
            value: 0.6

            contentItem: Item {
                scale: control.mirrored ? -1 : 1

                Rectangle {
                    height: parent.height
                    width: (control.indeterminate ? 1.0 : control.position) * parent.width

                    radius: 2
                    border.color: "#ccc"
                    color: "#1296ff"
                }

                Item {
                    x: 1; y: 1
                    width: parent.width - 2
                    height: parent.height - 2
                    visible: control.indeterminate
                    clip: true

                    ColorImage {
                        width: Math.ceil(parent.width / implicitWidth + 1) * implicitWidth
                        height: parent.height

                        mirror: control.mirrored
                        fillMode: Image.TileHorizontally
                        source: "/images/progressmask.png"
                        color: Color.transparent(Qt.lighter("#1296ff", 1.2), 160 / 255)

                        visible: control.indeterminate
                        NumberAnimation on x {
                            running: control.indeterminate && control.visible
                            from: -31 // progressmask.png width
                            to: 0
                            loops: Animation.Infinite
                            duration: 750
                        }
                    }
                }
            }

            background: Rectangle {
                radius: 2
                color: "#fff"
                border.color: "#ccc"
            }
        }

        Switch {
            text: "开启不确定模式"
            font.pointSize: 11
            checked: control.indeterminate
            onClicked: control.indeterminate = !control.indeterminate
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

思路一句话:不确定模式下手动让进度条占满,再在上面盖一层滚动条纹。

底层的蓝色矩形在 indeterminate 开启时宽度直接乘 1.0,也就是铺满整条。条纹是 ColorImage:加载 progressmask.png 这张斜纹蒙版图,fillMode: Image.TileHorizontally 让它横向平铺,color 属性把蒙版的白色区域染成半透明蓝------蒙版图本身只有黑白,真正显示什么颜色由这行决定。

滚动靠一个永不停歇的位移动画:NumberAnimation on x 从 -31 回到 0,循环执行。条纹宽度是"比容器宽一屏"(Math.ceil(...) * implicitWidth),这样从 -31 移到 0 的位移恰好无缝衔接,看起来就是无限循环的条纹爬行。clip: true 保证条纹只出现在进度条内部。

这两个 demo 的 Switch 一开,进度条就从"看得见进度的模式"切到"不知道进度的模式",indeterminateProgressBar 官方的状态位,自己实现时照用就行。

对比

样式 实现方式 适合场景
矩形自定义 Rectangle 自绘 外观完全自己掌控,要百分比文字
Material 风格 ProgressBarImpl + 主题色 想快速做出 Material 观感
不确定动画 蒙版图 + 平铺位移 进度未知,表示"正在忙"

运行验证

  1. 用 Qt Creator 打开 qml_progressbar/CMakeLists.txt
  2. Ctrl+R 运行;
  3. 拖动矩形自定义的滑块看平滑跟随;开 Material 和条纹版的开关,对比两种不确定动画的差异。

扩展复用方向

  • 胶囊形进度条换个颜色、加个渐变,就是一套自定义主题。
  • 条纹蒙版图可以自己画其他纹理(点状、格纹),color 染色逻辑不变。
  • 把三个 demo 的思路混用:Material 细条 + 条纹蒙版,就是带条纹的 Material 不确定进度条。

已验证环境

相关推荐
Quz1 小时前
QML 环形进度条:Canvas 绘制与多环进度
qt
熬夜苦读学习15 小时前
Qt--网络编程
开发语言·qt
上海安当技术16 小时前
C/S 桌面软件怎么接 UKey 强认证?C# SDK 与 C 动态库集成 WinForms/Qt/工控实战
c语言·qt·c#
雨田言炎18 小时前
五、Qt控件使用说明大全
开发语言·qt
熬夜苦读学习19 小时前
Qt--界面优化
开发语言·qt
Bryce学亮20 小时前
FileLine,基于 Qt 6 + QML 构建的跨平台文件传输与即时通讯工具
数据库·c++·人工智能·python·qt·github
sycmancia1 天前
Qt——复习篇painter
开发语言·qt
Quz1 天前
QML ProgressBar 基础用法:默认样式与平滑动画
qt
繁重的秋春1 天前
Qt6.0下的CMake的部署配置
qt·cmake