公司动态
第三节 QML布局与定位器
本章解决 QML 中界面元素放哪里、怎么放、窗口变化怎么办的问题。覆盖三种定位体系手动坐标 / 锚点 / 布局元素 / 定位器并给出决策建议与实战技巧。1. 坐标系统方式特点适用场景手动 x / y最自由需自己算拖拽、自由画布锚点 anchors声明式相对定位自适应强90% 的界面布局定位器 PositionersRow/Column/Grid/Flow同类型元素成排放置布局 LayoutsRowLayout/ColumnLayout/GridLayout支持伸缩系数复杂/可伸缩界面StackLayout同一区域多页面切换Tab 式内容StackView / SwipeView页面栈/滑动页多页面导航anchors Layout 混用极少特殊需求核心原则优先锚点与布局尽量少手算 x/y手算的代码在窗口变化/字体变化时最容易坏。每个 Item 有自己的局部坐标系x/y 是相对父对象左上角的偏移向右为 x向下为 y。width/height 默认为 0若不设置、又没有被布局/锚点撑开子元素会看不见常见坑。z 控制层叠顺序默认 0越大越靠上同一 z 下按声明顺序绘制后声明的在上层。Item { width: 400; height: 300 Rectangle { x: 10; y: 10; width: 100; height: 100; color: red } // 右上(10,10) Rectangle { x: 50; y: 50; width: 100; height: 100; color: blue } // 叠加在 red 上 }2. 锚点布局Anchors—— 最常用锚点可以理解为吸附关系把子项的边/中心吸附到父项或兄弟项的边/中心。锚点布局只影响位置不影响尺寸除非使用 fill / centerIn 之外的边组合。2.1 常用锚点属性锚点含义anchors.left / right / top / bottom绑定到某对象的对应边anchors.horizontalCenter / verticalCenter水平/垂直居中anchors.fill填满等价于四边都绑定anchors.centerIn中心居中等价于两个 centeranchors.margins四边统一外边距anchors.leftMargin / topMargin / ...单边外边距anchors.leftMargin 等与 anchors.margins 叠加单边优先2.2 基本用法Item { width: 400; height: 300 Rectangle { id: header width: parent.width height: 50 color: #3d5a80 } Text { anchors.top: header.bottom // 锚到 header 底部 anchors.topMargin: 8 anchors.horizontalCenter: parent.horizontalCenter // 水平居中 text: Header 下方 } Rectangle { id: footer width: parent.width height: 40 anchors.bottom: parent.bottom // 钉在父容器底部 color: #98c1d9 } }2.3 经典布局模式速查// 填满父级 anchors.fill: parent // 居中 anchors.centerIn: parent // 靠左上默认 x0,y0 就是左上通常无需锚 // 靠右下 anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: 12 // 两个元素并排右元素贴左元素右侧 RectA { id: a } RectB { anchors.left: a.right; anchors.leftMargin: 8; anchors.verticalCenter: a.verticalCenter } // 底部工具条 anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom // 两侧夹中间中间自动伸缩 leftRect { anchors.left: parent.left } rightRect { anchors.right: parent.right } centerRect { anchors.left: leftRect.right; anchors.right: rightRect.left }2.4 锚点与 margin 的细节Text { anchors.fill: parent anchors.margins: 10 // 统一边距 anchors.leftMargin: 20 // 单独覆盖左边距 }2.5 anchors 的五大陷阱速记版不要同时用锚点与 x/y 定位anchors 生效后 x/y 会被覆盖二者语义冲突。anchors.fill: parent 不会改变子项尺寸之外的几何——子项会铺满但如果父项尺寸为 0子项也看不见。锚点循环A 锚到 B、B 又锚到 A会导致引擎告警且行为未定义禁止循环。anchors 只能绑定一个目标多次赋值后者覆盖前者绑定是最后设置生效。控件尺寸自适应问题Button 有 implicitWidth锚到父项时通常要配合 fill 或显式宽高否则按内容撑开。3. 定位器PositionerRow / Column / Grid / Flow定位器是自动排列子项的容器适合一列按钮一行标签九宫格图标。3.1 Column —— 垂直排列Column { spacing: 10 // 子元素间距 anchors.centerIn: parent Button { text: 1 } Button { text: 2 } Button { text: 3 } }其他属性topPadding/bottomPadding/leftPadding/rightPadding、padding子项通过 Column.attach 附加属性没有单独设置。子项尺寸不显式给 width 时Column 会按最宽子项自动撑宽implicit给 height 时按内容。3.2 Row —— 水平排列Row { spacing: 8 anchors.centerIn: parent Button { text: A } Button { text: B } Button { text: C } }3.3 Grid —— 网格排列Grid { columns: 3 // 列数rows 与 columns 二选一 spacing: 12 // 统一间距 columnSpacing: 16 // 列间距覆盖 spacing rowSpacing: 8 // 行间距 anchors.centerIn: parent Repeater { model: 6 delegate: Rectangle { width: 50; height: 50 color: lightsteelblue border.color: steelblue Text { anchors.centerIn: parent; text: index } } } }3.4 Flow —— 流式排列自动换行Flow { anchors.fill: parent anchors.margins: 12 spacing: 8 Repeater { model: 12 delegate: Rectangle { width: 60 index * 5 // 宽度不一自动换行 height: 40 radius: 6 color: coral Text { anchors.centerIn: parent; text: index } } } }Flow 自动把放不下的子项换到下一行适合标签云、可变宽内容。3.5 定位器与 Repeater定位器配合 Repeater 最常用批量生成子项Row { spacing: 4 Repeater { model: 5 delegate: Rectangle { width: 30; height: 30 radius: 15 color: green } } }Repeater 的 delegate 中可访问index序号、modelData当前数据、model整个模型。3.6 定位器注意事项定位器会覆盖子对象的 x/y按位置摆放但不会覆盖 width/height。子对象手动设置 x/y 会被定位器忽略。定位器自身尺寸默认按子项内容计算 implicitWidth/implicitHeight若设置固定 width/height多出的空间按子项分布除非给 Layout.fill 等。4. QtQuick.Layouts —— 可拉伸的布局import QtQuick.LayoutsQt5 中 import QtQuick.Layouts 1.x。布局与定位器的核心区别布局支持伸缩系数Layout.fillWidth / Layout.fillHeight / Layout.preferredWidth能随窗口缩放动态分配空间。4.1 最小可用示例import QtQuick import QtQuick.Layouts Item { width: 400; height: 300 ColumnLayout { anchors.fill: parent anchors.margins: 12 spacing: 8 Rectangle { // 上可伸缩高度区域 Layout.fillWidth: true Layout.fillHeight: true Layout.minimumHeight: 60 color: #e63946 } RowLayout { // 下三列 Layout.fillWidth: true Layout.preferredHeight: 80 Rectangle { Layout.fillWidth: true; Layout.fillHeight: true; color: #f4a261 } Rectangle { Layout.fillWidth: true; Layout.fillHeight: true; color: #2a9d8f } } } }窗口拉伸时上方区域自动变高下方三列自动变宽——这是定位器做不到的。4.2 Layout 附加属性大全属性作用Layout.fillWidth占满父布局水平剩余空间boolLayout.fillHeight占满垂直剩余空间Layout.preferredWidth/Height理想尺寸优先于内部 implicitLayout.minimumWidth/Height最小尺寸防挤压Layout.maximumWidth/Height最大尺寸Layout.alignment对齐Qt.AlignHCenter 等Layout.margins / leftMargin/…外边距Layout.column/rowGridLayout 中指定行列Layout.columnSpan/rowSpan跨列/跨行Layout.fillWidth 为 true 的多个元素按 preferredWidth 比例分配空间4.3 伸缩系数详解多个 fillWidth: true 的元素按preferredWidth 比例分配剩余空间RowLayout { width: 300 Rectangle { Layout.fillWidth: true; Layout.preferredWidth: 1; height: 40; color: red } Rectangle { Layout.fillWidth: true; Layout.preferredWidth: 2; height: 40; color: green } Rectangle { Layout.fillWidth: true; Layout.preferredWidth: 1; height: 40; color: blue } } // 红:绿:蓝 ≈ 1:2:1如果要固定尺寸 弹性区域给固定元素不设 fillWidthRowLayout { width: 300 Rectangle { width: 60; height: 40; color: gray } // 固定 60 Rectangle { Layout.fillWidth: true; height: 40; color: yellow } // 吃掉剩余 }4.4 嵌套布局实战经典顶栏 内容 底栏import QtQuick import QtQuick.Controls import QtQuick.Layouts ApplicationWindow { width: 480; height: 360 visible: true title: 经典布局 ColumnLayout { anchors.fill: parent spacing: 0 // 顶栏 ToolBar { Layout.fillWidth: true RowLayout { anchors.fill: parent ToolButton { text: ☰ } Label { text: 标题; Layout.fillWidth: true } ToolButton { text: ⚙ } } } // 中部左侧导航 右侧内容 RowLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 Rectangle { Layout.preferredWidth: 140 Layout.fillHeight: true color: #f0f0f0 Column { anchors.fill: parent anchors.margins: 8 spacing: 4 Repeater { model: [首页, 设置, 关于] delegate: Button { width: parent.width - 16 text: modelData } } } } Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: white Text { anchors.centerIn: parent text: 内容区域 color: gray } } } // 底栏 Rectangle { Layout.fillWidth: true Layout.preferredHeight: 28 color: #e0e0e0 Text { anchors.centerIn: parent text: 状态栏 font.pixelSize: 12 color: #666 } } } }4.5 布局使用建议布局里不要混用 anchors除 anchors.fill 挂到父布局等少数情况混用会导致布局计算混乱。子项需固定尺寸直接 width/height 或 Layout.preferredWidth需要伸缩Layout.fillWidth/fillHeight。内容溢出时优先 clip: true 或用 ScrollView。GridLayout 适合表单Layout.row / Layout.column 精确控制位置。4.6 GridLayout 表单示例import QtQuick import QtQuick.Controls import QtQuick.Layouts GridLayout { columns: 2 anchors.centerIn: parent columnSpacing: 12 rowSpacing: 8 Label { text: 用户名: } TextField { Layout.preferredWidth: 200 placeholderText: 请输入用户名 } Label { text: 密码: } TextField { Layout.preferredWidth: 200 echoMode: TextInput.Password placeholderText: 请输入密码 } Label { text: 备注: } TextArea { Layout.preferredWidth: 200 Layout.preferredHeight: 80 placeholderText: 可选 } Item { } // 占位 Button { text: 登录; Layout.fillWidth: true } }5. 三套体系怎么选5. 其他布局/容器5.1 StackLayout —— 同一区域多页import QtQuick import QtQuick.Controls import QtQuick.Layouts Item { width: 300; height: 200 StackLayout { id: stack anchors.fill: parent currentIndex: tabBar.currentIndex Rectangle { color: #e63946; Text { anchors.centerIn: parent; text: 页 1 } } Rectangle { color: #f4a261; Text { anchors.centerIn: parent; text: 页 2 } } Rectangle { color: #2a9d8f; Text { anchors.centerIn: parent; text: 页 3 } } } TabBar { id: tabBar anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter TabButton { text: 1 } TabButton { text: 2 } TabButton { text: 3 } } }5.2 StackView —— 页面栈StackView { id: stackView anchors.fill: parent // 压入页面 function pushPage() { stackView.push(Qt.createComponent(Page2.qml)) // 或直接传字符串 Page2.qml } // 返回 pop() }5.3 SwipeView —— 滑动翻页SwipeView { anchors.fill: parent Page { Label { text: 第 1 页 } } Page { Label { text: 第 2 页 } } Page { Label { text: 第 3 页 } } }5.4 悬浮层与 z 值定位之外还要考虑层叠z 越大越靠上z 相同按声明顺序后声明的在上。Rectangle { z: 1; color: red; width: 100; height: 100 } Rectangle { z: 2; color: blue; x: 50; width: 100; height: 100 } // blue 在 red 上方5.5 百分比尺寸的替代方案不想写死百分比可以用 Layout.fillWidth 锚点组合或直接绑定Rectangle { width: parent.width * 0.25 } // 简易版需求选择元素跟随父项边缘 / 相对位置固定anchors最灵活简单的等间距排列、不拉伸Row / Column / Grid / Flow窗口尺寸变化时按比例分配空间RowLayout / ColumnLayout / GridLayout复杂网格合并单元格GridLayout经验法则能用 anchors 就用 anchors需要自适应窗口伸缩时切 Layouts不要混用锚点与 Layout会互相干扰。6. 自适应布局最佳实践从外到内设计先决定大区块顶栏/内容/底栏再细化内部。多态尺寸用 Layout.fillWidth/fillHeight 代替手算 width: parent.width - 200 之类。限制最小尺寸Layout.minimumWidth 防止小窗口挤压控件。测试极端尺寸把窗口拖到最小/最大、改变系统字体看布局是否崩坏。长文本要 wrapText 配 wrapMode: Text.Wrap maximumLineCount。不要嵌套过深过多嵌套影响性能与可读性优先扁平化 布局。固定 vs 弹性明确哪个元素该变、哪个不该变设计时就定下来。7. 布局常见坑width/height 未设置 → 元素不可见。anchors 与 x/y 混用 → 位置诡异。anchors 与 Layout.* 混用 → 布局引擎冲突告警。定位器内子项尺寸依赖内容未给宽度导致文本被截断。父项尺寸由内容决定如 Row 不设宽高时内部 anchors.fill: parent 会失效。8. 自测题anchors.fill: parent 后设置 width: 200 有效吗为什么Row 和 RowLayout 的本质区别是什么如何实现左侧固定 200px、右侧自动占满定位器与布局哪个支持按比例伸缩元素想锚到兄弟的兄弟怎么办