QML SplitView 进阶:嵌套布局与状态持久化

一个 SplitView 只能把区域分成两栏,遇到三分栏这种结构,就得在 SplitView 上再套一个 SplitView。

另外,用户把分隔条布局弄乱了,想临时一键恢复成上次保存好的样子。

本文用2个例子解决上述问题。

嵌套布局

外层水平分两栏,右侧再垂直分上下两栏,三层面板自由拖拽。如图所示:

演示代码

qml 复制代码
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    id: root
    property color mainColor: Theme.listColors[Theme.demo3]

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

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

        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 45
            color: Theme.demoStyle.frameBg
            radius: 6
            border.color: Theme.demoStyle.frameBorder
            border.width: 1

            RowLayout {
                anchors.fill: parent
                anchors.margins: 10
                spacing: 10

                Text {
                    text: "布局结构:"
                    font.pointSize: 10
                    color: Theme.demoStyle.frameText
                }

                Text {
                    text: "左侧面板 + 右侧(上下面板)"
                    font.pointSize: 10
                    color: root.mainColor
                    font.bold: true
                }

                Item { Layout.fillWidth: true }
            }
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Theme.demoStyle.frameBg
            border.color: Theme.demoStyle.frameBorder
            border.width: 1
            radius: 6

            SplitView {
                id: horizontalSplit
                anchors.fill: parent
                anchors.margins: 10
                orientation: Qt.Horizontal

                handle: Rectangle {
                    implicitWidth: 6
                    implicitHeight: 6
                    color: root.mainColor
                    opacity: SplitHandle.pressed ? 1.0 : 0.7
                }

                // 面板1
                CustomRect {
                    SplitView.minimumWidth: 60
                    SplitView.minimumHeight: 60
                    rectColor: Qt.color(root.mainColor).darker(1.0)
                    rectNum: "1"
                }

                SplitView {
                    id: verticalSplit
                    SplitView.fillWidth: true
                    SplitView.fillHeight: true
                    SplitView.minimumWidth: 80
                    SplitView.minimumHeight: 80
                    orientation: Qt.Vertical

                    handle: Rectangle {
                        implicitWidth: 6
                        implicitHeight: 6
                        color: root.mainColor
                        opacity: SplitHandle.pressed ? 1.0 : 0.7
                    }

                    // 面板2
                    CustomRect {
                        SplitView.minimumWidth: 80
                        SplitView.minimumHeight: 80
                        rectColor: Qt.color(root.mainColor).darker(1.2)
                        rectNum: "2"
                    }

                    // 面板3
                    CustomRect {
                        SplitView.minimumWidth: 80
                        SplitView.minimumHeight: 80
                        rectColor: Qt.color(root.mainColor).darker(1.4)
                        rectNum: "3"
                    }
                }
            }
        }
    }
}

嵌套的写法就是把一个 SplitView 当成普通子项,写进另一个 SplitView 里。

这里有两个新手容易踩的坑

内层 SplitView 作为子项,一定要给自己配 SplitView.fillWidth / SplitView.fillHeight(或者至少给个 SplitView.minimumWidth),否则外层不会给它分配空间,右边的区域会塌掉;每个面板和每个嵌套层最好也都写上最小尺寸,拖拽时才不会把某一栏拖没。

拖拽是联动的,拖外层分隔条,整个右侧区域一起变宽变窄;拖内层分隔条,只影响右栏上下两部分。嵌套的层级就是布局的层级,逐层理解就不会乱。

适用场景:IDE 式多面板界面、复杂工作区、编辑器类布局。

状态持久化

把拖好的面板比例保存起来,下次一键恢复。如图所示:

演示代码

qml 复制代码
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    id: root
    property color mainColor: Theme.listColors[Theme.demo4]

    property var savedState: null
    property bool hasSavedState: false
    property bool splitChanged: false

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

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

        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 50
            color: Theme.demoStyle.frameBg
            radius: 6
            border.color: Theme.demoStyle.frameBorder
            border.width: 1

            RowLayout {
                anchors.fill: parent
                anchors.margins: 10
                spacing: 10

                Text {
                    text: "状态:"
                    font.pointSize: 10
                    color: Theme.demoStyle.frameText
                }

                // 显示保存状态
                Text {
                    text: root.hasSavedState ? "已保存" : "未保存"
                    font.pointSize: 10
                    font.bold: true
                    color: root.mainColor
                }

                Item {
                    Layout.fillWidth: true
                }

                // 在移动splitview时,状态闪烁
                Text {
                    text: "Resizing"
                    font.pointSize: 10
                    font.bold: true
                    color: root.mainColor
                    visible: splitView.resizing

                    SequentialAnimation on opacity {
                        loops: Animation.Infinite
                        running: splitView.resizing
                        NumberAnimation { to: 0.3; duration: 500 }
                        NumberAnimation { to: 1.0; duration: 500 }
                    }
                }

                CustomButton {
                    text: "保存状态"
                    // 解决第一次保存失效的问题,必须先移动splitview
                    enabled: root.splitChanged
                    bgNormal: root.mainColor
                    bgPressed: Qt.color(root.mainColor).darker(1.2)
                    onClicked: {
                        root.savedState = splitView.saveState()
                        root.hasSavedState = true
                        console.log("SplitView状态已保存")
                    }
                }

                CustomButton {
                    text: "恢复状态"
                    enabled: root.hasSavedState
                    bgNormal: root.mainColor
                    bgPressed: Qt.color(root.mainColor).darker(1.2)
                    onClicked: {
                        if (root.savedState) {
                            splitView.restoreState(root.savedState)
                            console.log("SplitView状态已恢复")
                        }
                    }
                }
            }
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Theme.demoStyle.frameBg
            border.color: Theme.demoStyle.frameBorder
            border.width: 1
            radius: 6

            SplitView {
                id: splitView
                anchors.fill: parent
                anchors.margins: 10
                orientation: Qt.Horizontal

                handle: Rectangle {
                    implicitWidth: 6
                    implicitHeight: 6
                    color: root.mainColor
                    opacity: SplitHandle.pressed ? 1.0 : 0.7
                }

                onResizingChanged: {
                    root.splitChanged = true
                }

                // 面板1
                CustomRect {
                    SplitView.minimumWidth: 60
                    rectColor: Qt.color(root.mainColor).darker(0.9)
                    rectNum: "1"
                }

                // 面板2
                CustomRect {
                    SplitView.fillWidth: true
                    SplitView.minimumWidth: 60
                    rectColor: Qt.color(root.mainColor).darker(1.1)
                    rectNum: "2"
                }

                // 面板3
                CustomRect {
                    SplitView.minimumWidth: 60
                    rectColor: Qt.color(root.mainColor).darker(1.3)
                    rectNum: "3"
                }
            }
        }
    }
}

SplitView 自带两个方法:saveState() 把当前所有分隔条的位置打包成一个对象,restoreState() 按这个对象恢复布局。

demo 里"保存状态"按钮把结果存进 savedState,"恢复状态"按钮再把它交还给 restoreState(),拖好的比例就回来了。

demo演示的是临时处理,把记住布局真正落地,还要把 savedState 序列化存到本地(比如转成 JSON 写进设置文件),程序启动时读出来调 restoreState(),用户上次拖好的布局就自动回来了。

适用场景:编辑器布局记忆、窗口布局偏好、多面板工具的个性化设置。


已验证环境:

相关推荐
时间的拾荒人2 小时前
Qt 显示类控件详解:从 QLabel 到 QCalendarWidget 的实战指南
开发语言·qt·面试
kaixin_learn_qt_ing10 小时前
Qt插件机制demo示例
qt
Quz13 小时前
QML Flickable:内容滚动与边界行为
qt
Quz13 小时前
QML Flipable:卡片翻转与登录设置
qt
阿钱真强道20 小时前
24 嵌入式操作系统 | 项目启动与环境准备(PyQt5 / Flask 两个“最小可跑“)
开发语言·qt·flask·架构设计·协议契约
物极必反10241 天前
QT使用FFMpeg编译安卓程序.md
qt·ffmpeg·安卓
励志不掉头发的内向程序员1 天前
【从零写一个CAD 03】三个 double 值得单独一个类吗:把视图变换抽成 View
开发语言·c++·qt·学习·系统架构
sycmancia2 天前
Qml——第一个qml的程序
qt
实心儿儿2 天前
Qt — Qt 多线程
开发语言·qt