Qt Quick 布局Positioners、Anchors 与 Layouts(九)

5 阅读7分钟

适合人群: 已掌握 Qt Quick Controls 基础,想让 UI 自适应不同屏幕尺寸的开发者


前言

写出来的界面能跑,但一调整窗口大小就乱掉——这是 Qt Quick 新手最常遇到的问题。根本原因是没有掌握布局系统。

Qt Quick 提供了三套定位与布局机制,各有适用场景:

机制模块特点
anchorsQtQuick 内置基于边对齐,灵活但不自动分配尺寸
Positioners(RowColumnGridFlowQtQuick 内置自动排列,固定间距,不管理尺寸
Layouts(RowLayoutColumnLayoutGridLayoutQtQuick.Layouts自动排列 + 管理尺寸,响应式首选

本文按从简到难的顺序,把三套机制都讲透。


一、anchors 深入

上一篇简单介绍过 anchors,这里补全所有细节。

1.1 锚点属性完整列表

Item {
    anchors.left:              // 左边
    anchors.right:             // 右边
    anchors.top:               // 上边
    anchors.bottom:            // 下边
    anchors.horizontalCenter:  // 水平中线
    anchors.verticalCenter:    // 垂直中线
    anchors.baseline:          // 文字基线(Text 元素专用)

    anchors.fill:              // 填满某个元素(等同于同时设置四边)
    anchors.centerIn:          // 居中于某个元素

    // 边距
    anchors.margins:           // 四边统一边距
    anchors.leftMargin:        // 单独左边距
    anchors.rightMargin:
    anchors.topMargin:
    anchors.bottomMargin:
}

1.2 常用布局模式

顶部导航栏 + 剩余内容区:

Rectangle {
    id: navbar
    height: 56
    anchors {
        top: parent.top
        left: parent.left
        right: parent.right
    }
    color: "#4A90E2"
}

Rectangle {
    anchors {
        top: navbar.bottom
        left: parent.left
        right: parent.right
        bottom: parent.bottom
    }
    color: "#f5f5f5"
}

左侧边栏 + 右侧内容区:

Rectangle {
    id: sidebar
    width: 200
    anchors {
        top: parent.top
        left: parent.left
        bottom: parent.bottom
    }
    color: "#2C3E50"
}

Rectangle {
    anchors {
        top: parent.top
        left: sidebar.right
        right: parent.right
        bottom: parent.bottom
    }
    color: "#ECF0F1"
}

1.3 anchors 的限制

  • 不能自动分配剩余空间:无法让两个元素平分父容器宽度
  • 不能跨层级锚定:只能锚定父元素或同级兄弟元素
  • 与 Layouts 冲突:放在 Layout 内的子元素不要使用 anchors

遇到这些情况,改用 Layouts。


二、Positioners:快速排列元素

Positioners 适合子元素尺寸固定、只需要自动排列间距的场景。

2.1 Row — 水平排列

import QtQuick

Row {
    spacing: 12

    Rectangle { width: 80; height: 80; color: "#4A90E2"; radius: 8 }
    Rectangle { width: 80; height: 80; color: "#E2934A"; radius: 8 }
    Rectangle { width: 80; height: 80; color: "#4AE29A"; radius: 8 }
}

RTL(从右到左)语言支持:

Row {
    spacing: 8
    layoutDirection: Qt.RightToLeft
}

2.2 Column — 垂直排列

Column {
    spacing: 8
    width: 280

    TextField { width: parent.width; placeholderText: "用户名" }
    TextField { width: parent.width; placeholderText: "密码"; echoMode: TextInput.Password }
    Button    { width: parent.width; text: "登录" }
}

2.3 Grid — 网格排列

Grid {
    columns: 3
    spacing: 10

    Repeater {
        model: 9
        delegate: Rectangle {
            width: 80; height: 80
            color: Qt.rgba(0.2 + index * 0.08, 0.4, 0.8, 1)
            radius: 8

            Text {
                anchors.centerIn: parent
                text: index + 1
                color: "white"
                font.bold: true
                font.pixelSize: 18
            }
        }
    }
}

2.4 Flow — 流式排列(自动换行)

Flow 是最灵活的 Positioner,子元素从左到右排列,空间不足时自动换行:

Flow {
    width: 360
    spacing: 8

    Repeater {
        model: ["Qt Quick", "QML", "跨平台", "嵌入式", "移动端",
                "桌面开发", "C++", "JavaScript", "动画", "UI设计"]
        delegate: Rectangle {
            width: tagText.width + 20
            height: 28
            radius: 14
            color: "#E6F1FB"
            border.width: 1
            border.color: "#B5D4F4"

            Text {
                id: tagText
                anchors.centerIn: parent
                text: modelData
                color: "#185FA5"
                font.pixelSize: 13
            }
        }
    }
}

2.5 Positioners 的局限

Positioners 不管理子元素的尺寸——子元素需要自己设置 widthheight,Positioners 只负责摆放位置。想要自动分配尺寸,用 Layouts。


三、Layouts:响应式布局的核心

import QtQuick.Layouts

3.1 RowLayout

RowLayout {
    width: 400
    height: 48
    spacing: 8

    Button {
        text: "取消"
        Layout.preferredWidth: 80
    }

    Item {
        Layout.fillWidth: true    // 弹性空间,把后面的按钮推到右边
    }

    Button {
        text: "保存草稿"
        Layout.preferredWidth: 100
    }

    Button {
        text: "发布"
        highlighted: true
        Layout.preferredWidth: 80
    }
}

3.2 ColumnLayout

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

    Label { text: "文章标题"; font.bold: true }

    TextField {
        Layout.fillWidth: true
        placeholderText: "请输入标题"
    }

    Label { text: "正文内容" }

    ScrollView {
        Layout.fillWidth: true
        Layout.fillHeight: true    // 填满剩余高度
        TextArea {
            placeholderText: "在此输入正文..."
            wrapMode: TextArea.Wrap
        }
    }

    RowLayout {
        Layout.fillWidth: true
        spacing: 8
        Button { text: "取消"; Layout.fillWidth: true }
        Button { text: "发布"; highlighted: true; Layout.fillWidth: true }
    }
}

3.3 GridLayout

GridLayout {
    width: 360
    columns: 2
    columnSpacing: 16
    rowSpacing: 12

    Label { text: "姓名" }
    TextField { Layout.fillWidth: true; placeholderText: "请输入" }

    Label { text: "邮箱" }
    TextField { Layout.fillWidth: true; placeholderText: "请输入" }

    Label { text: "手机" }
    TextField { Layout.fillWidth: true; placeholderText: "请输入" }

    Button {
        Layout.columnSpan: 2
        Layout.fillWidth: true
        text: "提交"
        highlighted: true
    }
}

3.4 Layout 附加属性完整参考

属性说明
Layout.fillWidth: true填满布局剩余宽度
Layout.fillHeight: true填满布局剩余高度
Layout.preferredWidth: 120期望宽度
Layout.preferredHeight: 44期望高度
Layout.minimumWidth: 80最小宽度,不会被压缩到此以下
Layout.maximumWidth: 200最大宽度,不会被拉伸到此以上
Layout.columnSpan: 2跨列数(GridLayout 专用)
Layout.rowSpan: 2跨行数(GridLayout 专用)
Layout.alignment: Qt.AlignRight格子内的对齐方式
Layout.margins: 8外边距

四、响应式布局:适配不同屏幕

4.1 用属性绑定实现断点

ApplicationWindow {
    id: window
    width: 800; height: 500
    visible: true

    // 定义断点属性
    readonly property bool isNarrow: width < 480
    readonly property bool isMedium: width >= 480 && width < 800
    readonly property bool isWide:   width >= 800

    GridLayout {
        anchors.fill: parent
        anchors.margins: 16
        columns: isNarrow ? 1 : isMedium ? 2 : 3    // 自动切换列数
        columnSpacing: 12
        rowSpacing: 12

        Repeater {
            model: 6
            delegate: Rectangle {
                Layout.fillWidth: true
                height: 100
                radius: 8
                color: "#4A90E2"
                opacity: 0.6 + index * 0.07

                Text {
                    anchors.centerIn: parent
                    text: "模块 " + (index + 1)
                    color: "white"
                    font.pixelSize: 16
                    font.bold: true
                }
            }
        }
    }
}

拖动窗口调整宽度,网格列数在 1、2、3 之间自动切换。

4.2 侧边栏折叠效果

ApplicationWindow {
    id: window
    width: 900; height: 600
    visible: true

    RowLayout {
        anchors.fill: parent
        spacing: 0

        // 侧边栏:窗口窄时自动折叠
        Rectangle {
            Layout.preferredWidth: window.width < 600 ? 56 : 200
            Layout.fillHeight: true
            color: "#1A2332"

            Behavior on Layout.preferredWidth {
                NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
            }

            Text {
                anchors.centerIn: parent
                text: window.width < 600 ? "≡" : "侧边栏"
                color: "white"
                font.pixelSize: window.width < 600 ? 22 : 16
            }
        }

        // 主内容区
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#F5F7FA"

            Text {
                anchors.centerIn: parent
                text: "主内容区\n当前宽度:" + Math.round(parent.width)
                color: "#666"
                font.pixelSize: 14
                horizontalAlignment: Text.AlignHCenter
            }
        }
    }
}

4.3 Flow 卡片自动换行

ScrollView {
    anchors.fill: parent

    Flow {
        width: parent.width
        padding: 16
        spacing: 12

        Repeater {
            model: 12
            delegate: Rectangle {
                width: 160; height: 120
                radius: 10
                color: "#ffffff"
                border.width: 1
                border.color: "#e8e8e8"

                Column {
                    anchors.centerIn: parent
                    spacing: 8

                    Rectangle {
                        width: 36; height: 36; radius: 18
                        color: "#4A90E2"
                        anchors.horizontalCenter: parent.horizontalCenter
                    }

                    Text {
                        text: "功能 " + (index + 1)
                        font.pixelSize: 13
                        font.bold: true
                        color: "#333"
                        anchors.horizontalCenter: parent.horizontalCenter
                    }
                }
            }
        }
    }
}

五、综合示例:响应式仪表盘框架

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

ApplicationWindow {
    id: window
    width: 900; height: 600
    minimumWidth: 360
    visible: true
    title: "响应式仪表盘"

    readonly property bool compact: width < 600

    RowLayout {
        anchors.fill: parent
        spacing: 0

        // 侧边栏
        Rectangle {
            Layout.preferredWidth: compact ? 56 : 200
            Layout.fillHeight: true
            color: "#1A2332"

            Behavior on Layout.preferredWidth {
                NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
            }

            Column {
                anchors.top: parent.top
                anchors.topMargin: 16
                width: parent.width
                spacing: 2

                // Logo
                Rectangle {
                    width: parent.width; height: 48
                    color: "transparent"
                    Text {
                        anchors.centerIn: parent
                        text: compact ? "D" : "Dashboard"
                        color: "white"
                        font.pixelSize: compact ? 18 : 16
                        font.bold: true
                    }
                }

                Rectangle {
                    width: parent.width; height: 1
                    color: "#ffffff20"
                }

                // 菜单
                Repeater {
                    model: ["概览", "数据", "报告", "设置"]
                    delegate: Rectangle {
                        width: parent.width; height: 44
                        color: index === 0 ? "#ffffff18" : "transparent"

                        Text {
                            anchors.centerIn: parent
                            text: compact ? modelData[0] : modelData
                            color: index === 0 ? "white" : "#ffffff70"
                            font.pixelSize: 14
                        }
                    }
                }
            }
        }

        // 主内容区
        ColumnLayout {
            Layout.fillWidth: true
            Layout.fillHeight: true
            spacing: 0

            // 顶栏
            Rectangle {
                Layout.fillWidth: true
                height: 56
                color: "white"

                RowLayout {
                    anchors.fill: parent
                    anchors.leftMargin: 20
                    anchors.rightMargin: 20

                    Label {
                        text: "概览"
                        font.pixelSize: 18
                        font.bold: true
                    }

                    Item { Layout.fillWidth: true }

                    Label {
                        text: Qt.formatDate(new Date(), "yyyy-MM-dd")
                        color: "#999"
                        font.pixelSize: 13
                    }
                }
            }

            // 内容滚动区
            ScrollView {
                Layout.fillWidth: true
                Layout.fillHeight: true
                contentWidth: availableWidth

                ColumnLayout {
                    width: parent.width
                    spacing: 16

                    Item { height: 4 }

                    // 统计卡片
                    GridLayout {
                        Layout.fillWidth: true
                        Layout.leftMargin: 16
                        Layout.rightMargin: 16
                        columns: compact ? 2 : 4
                        columnSpacing: 12
                        rowSpacing: 12

                        Repeater {
                            model: [
                                { label: "用户总数", value: "12,480", accent: "#4A90E2" },
                                { label: "今日活跃", value: "3,291",  accent: "#1D9E75" },
                                { label: "新增订单", value: "847",    accent: "#E2934A" },
                                { label: "总收入",   value: "¥52K",   accent: "#9B59B6" }
                            ]
                            delegate: Rectangle {
                                Layout.fillWidth: true
                                height: 88
                                radius: 10
                                color: "white"
                                border.width: 1
                                border.color: "#f0f0f0"

                                Rectangle {
                                    width: 4; height: 36; radius: 2
                                    color: modelData.accent
                                    anchors {
                                        left: parent.left; leftMargin: 14
                                        verticalCenter: parent.verticalCenter
                                    }
                                }

                                Column {
                                    anchors {
                                        left: parent.left; leftMargin: 28
                                        verticalCenter: parent.verticalCenter
                                    }
                                    spacing: 4
                                    Text {
                                        text: modelData.label
                                        font.pixelSize: 12; color: "#999"
                                    }
                                    Text {
                                        text: modelData.value
                                        font.pixelSize: 20; font.bold: true; color: "#222"
                                    }
                                }
                            }
                        }
                    }

                    // 图表占位(窄屏隐藏右侧饼图)
                    RowLayout {
                        Layout.fillWidth: true
                        Layout.leftMargin: 16
                        Layout.rightMargin: 16
                        spacing: 12

                        Rectangle {
                            Layout.fillWidth: true
                            height: 180; radius: 10
                            color: "white"
                            border.width: 1; border.color: "#f0f0f0"
                            Text {
                                anchors.centerIn: parent
                                text: "折线图区域"
                                color: "#ccc"; font.pixelSize: 14
                            }
                        }

                        Rectangle {
                            visible: !compact
                            Layout.preferredWidth: 200
                            height: 180; radius: 10
                            color: "white"
                            border.width: 1; border.color: "#f0f0f0"
                            Text {
                                anchors.centerIn: parent
                                text: "饼图区域"
                                color: "#ccc"; font.pixelSize: 14
                            }
                        }
                    }

                    Item { height: 16 }
                }
            }
        }
    }
}

六、三套机制选型指南

image.png

实际项目常见组合:

  • 页面整体框架用 ColumnLayout(顶栏 + 内容区)
  • 卡片网格用 GridLayout + 断点属性(自动切换列数)
  • 标签、按钮组用 Flow(自动换行)
  • 卡片内部元素用 anchors 精确定位
  • 工具栏按钮用 RowLayout + Item { Layout.fillWidth: true } 推到右边

总结

机制管理尺寸自动换行典型场景
anchors精确定位、顶底栏、填满父容器
Row / Column固定尺寸元素的快速排列
Grid固定尺寸元素的网格排列
Flow标签云、卡片流式布局
RowLayout工具栏、按钮组、水平自适应
ColumnLayout表单、页面主框架、垂直自适应
GridLayout表单标签对、响应式卡片网格

参考资料:Qt Academy — Positioners and Layouts · Qt Quick Layouts 文档