适合人群: 已掌握 Qt Quick Controls 基础,想让 UI 自适应不同屏幕尺寸的开发者
前言
写出来的界面能跑,但一调整窗口大小就乱掉——这是 Qt Quick 新手最常遇到的问题。根本原因是没有掌握布局系统。
Qt Quick 提供了三套定位与布局机制,各有适用场景:
| 机制 | 模块 | 特点 |
|---|---|---|
anchors | QtQuick 内置 | 基于边对齐,灵活但不自动分配尺寸 |
Positioners(Row、Column、Grid、Flow) | QtQuick 内置 | 自动排列,固定间距,不管理尺寸 |
Layouts(RowLayout、ColumnLayout、GridLayout) | QtQuick.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 不管理子元素的尺寸——子元素需要自己设置 width 和 height,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 }
}
}
}
}
}
六、三套机制选型指南
实际项目常见组合:
- 页面整体框架用
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 文档