QML Slider 组件:基础滑块、自定义手柄、刻度标记与自定义轨道

这篇介绍 QML 里 Slider 的几种常见用法:从最基础的一行滑块,到改手柄、加刻度、完全自定义轨道。看完这几个例子,基本能覆盖日常 UI 里 80% 的 Slider 需求。

  • Slider 基础 --- 认识 from/to/value/stepSize,再顺手切个垂直方向
  • 自定义手柄 --- 用 handle 属性把默认手柄换成圆点样式
  • 刻度标记 --- 用 Repeater 在滑块下面画刻度和数字
  • 自定义轨道 --- 用 background 属性同时控制背景轨道和已完成进度

Demo 1:Slider 基础

一个最普通的水平滑块,加上一个 Switch 切换水平/垂直方向。

演示代码

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

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

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

        Slider {
            id: control
            Layout.fillWidth: true
            Layout.preferredHeight: 76

            from: 0
            to: 100
            value: 50
            stepSize: 5
        }

        RowLayout {
            Layout.fillWidth: true
            spacing: 15

            Switch {
                text: "垂直方向"
                font.pointSize: 10
                checked: control.orientation === Qt.Vertical
                onClicked: {
                    control.orientation === Qt.Vertical ?
                                control.orientation = Qt.Horizontal :
                                control.orientation = Qt.Vertical;
                }
            }

            Text {
                text: "当前值: " + control.value
                font.pointSize: 10
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

Slider 最核心的属性就四个:

  • from / to:取值范围
  • value:当前值
  • stepSize:每次变化的步长,这里设成 5,拖动时就会按 0、5、10...这样跳

垂直切换靠的是 orientation 属性。默认是 Qt.Horizontal,把它改成 Qt.Vertical 就行。注意垂直时建议给 Slider 一个固定高度,不然 Layout.fillWidth 撑开后看起来会很小。

Demo 2:自定义手柄

默认手柄是个小圆角矩形,如果想改成带中心圆点的样式,就重写 handle

演示代码

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

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

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

        Slider {
            id: control
            Layout.fillWidth: true

            from: 0
            to: 100
            value: 50
            stepSize: 5

            handle: Rectangle {
                x: parent.leftPadding + (parent.horizontal ? parent.visualPosition * (parent.availableWidth - width) : (parent.availableWidth - width) / 2)
                y: parent.topPadding + (parent.horizontal ? (parent.availableHeight - height) / 2 : parent.visualPosition * (parent.availableHeight - height))
                width: 24
                height: 24
                radius: 12
                color: parent.pressed ? "#f0f0f0" : "#ffffff"
                border.color: "#cccccc"

                Rectangle {
                    anchors.centerIn: parent
                    width: 8
                    height: 8
                    radius: 4
                    color: "#21be2b"
                }
            }
        }

        Text {
            text: "当前值: " + control.value
            font.pointSize: 11
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

handleSlider 开放出来的一个槽位,你可以把整个手柄替换成任意 Item。这里有两个细节容易踩坑:

  • 手柄位置不能写死,得用 visualPosition。它是 0~1 的相对位置,乘以 availableWidth - width 就能算出正确偏移。
  • 要兼容水平和垂直方向。parent.horizontal 为 true 时按 x 轴算;垂直时按 y 轴算,x 轴居中。

另外 parent.pressed 可以判断手柄是否被按下,拿来做按下变色很合适。

Demo 3:刻度标记

有些场景需要让 Slider 停在固定刻度上,比如音量 0~100 每 20 一档。这时除了 stepSizesnapMode,还要在视觉上标出刻度。

演示代码

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

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

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

        Column {
            Layout.fillWidth: true
            spacing: 0
            height: 40

            Slider {
                id: slider
                width: parent.width
                from: 0
                to: 100
                value: 20
                stepSize: 20
                snapMode: Slider.SnapAlways
            }

            Item {
                width: parent.width
                height: 30

                Repeater {
                    model: 6  // 0到100,每20一个刻度,共6个数字

                    Column {
                        x: slider.leftPadding + index * ((slider.availableWidth) / 5) - width/2
                        spacing: 2

                        Rectangle {
                            anchors.horizontalCenter: parent.horizontalCenter
                            width: 2
                            height: 8
                            color: "#888"
                        }

                        Text {
                            text: (index * 20).toString()
                            color: "#888"
                            font.pointSize: 10
                        }
                    }
                }
            }
        }

        Text {
            text: "当前值: " + slider.value
            font.pointSize: 11
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

刻度不是 Slider 自带的,需要自己在滑块下方画。思路是用 Repeater 生成一组刻度和文字,再用 index 计算每个刻度的 x 坐标。

坐标公式:slider.leftPadding + index * (slider.availableWidth / 5) - width / 2

  • leftPadding 是 Slider 左右内边距,从这里开始算才不会偏。
  • availableWidth 是 Slider 实际可拖动的宽度。
  • 除以 5 是因为 0~100 之间有 5 个间隔。
  • 最后减 width / 2 把刻度组居中到目标点。

snapMode: Slider.SnapAlways 保证松手后一定回到最近刻度,不会停在中间。

Demo 4:自定义轨道

如果想同时改轨道背景、已完成进度和手柄,就得重写 backgroundhandle

演示代码

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

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

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

        Text {
            Layout.alignment: Qt.AlignHCenter
            text: Math.round(control.value)
            color: "#333"
            font.pointSize: 12
        }

        Slider {
            id: control
            Layout.fillWidth: true
            Layout.preferredHeight: 30

            from: 0
            to: 100
            value: 50

            background: Rectangle {
                x: control.leftPadding
                y: control.topPadding + control.availableHeight / 2 - height / 2
                width: control.availableWidth
                height: 4
                radius: 2
                color: "#34495e"

                Rectangle {
                    width: control.visualPosition * parent.width
                    height: parent.height
                    color: "#2ecc71"
                    radius: 2
                }
            }

            handle: Rectangle {
                x: control.leftPadding + control.visualPosition * (control.availableWidth - width)
                y: control.topPadding + control.availableHeight / 2 - height / 2
                width: 20
                height: 20
                radius: 10
                color: control.pressed ? "#13a333" : "#ecf0f1"
                border.color: "#2ecc71"
                border.width: 2

                Behavior on color {
                    ColorAnimation { duration: 100 }
                }
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

background 里放两层 Rectangle:一层做深色轨道,一层做绿色进度。进度宽度用 control.visualPosition * parent.width,这样拖动时会实时跟随。

手柄的 x 坐标公式和 Demo 2 一样,但因为这里确定是水平滑块,所以可以直接写:

qml 复制代码
x: control.leftPadding + control.visualPosition * (control.availableWidth - width)

Behavior on color 加了一个 100ms 的颜色过渡,按下和松开时颜色变化会更柔和。

运行验证

  1. 用 Qt Creator 打开 qml_slider/CMakeLists.txt
  2. Ctrl+R 运行;
  3. 左侧导航切换四个 demo,拖动滑块看效果。

扩展复用方向

  • 把 Demo 4 的进度条颜色改成渐变,直接用 Gradient 实现。
  • 在 Demo 3 的刻度上方加当前值气泡,拖动时显示具体数值。
  • 给手柄加阴影或放大效果,用 DropShadow 或改变 scale 实现。

已验证环境

相关推荐
dok125 小时前
zynq qt 交叉编译
开发语言·qt
龚建波18 小时前
《QDebug 2026年6月》
qt
△曉風殘月〆20 小时前
如何在Linux中安装Qt开发环境
linux·运维·qt
Quz1 天前
QML 选择控件:Switch、CheckBox 与 RadioButton
qt
熬夜苦读学习1 天前
Qt常用控件
服务器·开发语言·qt
夏玉林的学习之路1 天前
Qt 项目编译错误排查与解决记录
开发语言·qt
Quz2 天前
QML 基础按钮:Button、RoundButton 与 DelayButton
qt
Quz2 天前
QML ToolTip 组件:图标、多行文本与阴影
qt
xcyxiner2 天前
DicomViewer14(读取图像按固定窗宽窗位显示)
qt