编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

    • Linux应用开发
    • QML基础入门

      • QML基础入门
      • 嵌入式GUI技术全景
      • QML引擎与渲染原理
      • QML语法与类型系统
      • 属性绑定与响应式原理
      • 可视元素与布局原理
        • 5.1 案例引入
          • 5.1.1 布局灾难
          • 5.1.2 根因分析
          • 5.1.3 三大问题
        • 5.2 Item 基类
          • 5.2.1 几何属性
          • 5.2.2 可见裁剪
          • 5.2.3 SG 映射
        • 5.3 基础元素
          • 5.3.1 四种元素
          • 5.3.2 底层节点
        • 5.4 锚布局原理
          • 5.4.1 约束求解
          • 5.4.2 优先级冲突
          • 5.4.3 锚点对比
          • 5.4.4 锚布局案例
        • 5.5 定位器
          • 5.5.1 四种定位器
          • 5.5.2 复杂度控制
        • 5.6 布局管理器
          • 5.6.1 三种布局
          • 5.6.2 选择策略
        • 5.7 多屏仪表盘
        • 5.8 新手陷阱
        • 5.9 训练题
        • 5.10 速查表
      • 事件处理与传播机制
      • 模型视图架构原理
      • 动画与状态机原理
      • Canvas与自定义渲染
      • QML与C++集成原理
      • 自定义SceneGraph节点
      • 交叉编译与部署
      • 嵌入式渲染后端
      • 性能优化与真机调试
    • QT核心库实践

    • Linux系统编程

    • 综合项目实战

  • IoT智能硬件开发

  • Apps
  • Linux应用开发
  • QML基础入门
杨充
2025-06-24
目录

可视元素与布局原理

# 第 5 章 可视元素与布局原理

本章定位:从"摆控件"到"懂布局引擎"。前面你学会了语法和绑定,本章系统掌握 Item/Rectangle/Text 等可视元素的特性、anchors 布局约束求解、以及 RowLayout/GridLayout 的动态计算——理解这些,才写得出一套代码适配 480p 到 4K 所有分辨率。

# 目录介绍

  • 5.1 案例引入
    • 5.1.1 布局灾难
    • 5.1.2 根因分析
    • 5.1.3 三大问题
  • 5.2 Item 基类
    • 5.2.1 几何属性
    • 5.2.2 可见裁剪
    • 5.2.3 SG 映射
  • 5.3 基础元素
    • 5.3.1 四种元素
    • 5.3.2 底层节点
  • 5.4 锚布局原理
    • 5.4.1 约束求解
    • 5.4.2 优先级冲突
    • 5.4.3 锚点对比
    • 5.4.4 锚布局案例
  • 5.5 定位器
    • 5.5.1 四种定位器
    • 5.5.2 复杂度控制
  • 5.6 布局管理器
    • 5.6.1 三种布局
    • 5.6.2 选择策略
  • 5.7 多屏仪表盘
  • 5.8 新手陷阱
  • 5.9 训练题
  • 5.10 速查表

# 5.1 案例引入

# 5.1.1 布局灾难

某车机 HMI 工程师在 1920×720 横屏上设计了仪表盘,老板说"中控也要跑这套 UI,但是竖屏 720×1280"。他以为改个 width/height 就行:

Rectangle {
    id: root
    width: 1920; height: 720

    Rectangle {
        id: speedPanel
        x: 20; y: 20
        width: 400; height: 300             // ⚠️ 硬编码
    }

    Rectangle {
        id: mapPanel
        x: 440; y: 20
        width: parent.width - 460; height: 680  // 半硬编码
    }

    Rectangle {
        id: statusBar
        x: 20; y: 640
        width: parent.width - 40; height: 60    // ⚠️ 硬编码
    }
}

切到竖屏 width: 720; height: 1280:

  • speedPanel 仍然在 (20,20) OK
  • mapPanel 的 x: 440 跟 speedPanel 重叠了(speedPanel 宽 400,间距只有 20)
  • statusBar 的 y: 640 差得远——竖屏高度 1280,statusBar 悬在半空
  • 更糟:没人敢改这些硬编码数字——它们来自半年前离职的工程师

# 5.1.2 根因分析

这个布局如果用 QML 的 anchors 重写:

Rectangle {
    id: root
    width: 720; height: 1280

    Rectangle {
        id: speedPanel
        anchors { left: parent.left; top: parent.top; margins: 20 }
        width: parent.width * 0.3; height: 300
    }

    Rectangle {
        id: mapPanel
        anchors {
            left: speedPanel.right; leftMargin: 20
            top: parent.top; topMargin: 20
            right: parent.right; rightMargin: 20
            bottom: statusBar.top; bottomMargin: 20
        }
        // ← 无硬编码坐标,所有关系由 anchors 约束求解
    }

    Rectangle {
        id: statusBar
        anchors {
            left: parent.left; right: parent.right
            bottom: parent.bottom
            margins: 20
        }
        height: 60
    }
}

切到竖屏 → 一行不改。anchors 引擎自动重新求解所有约束。

# 5.1.3 三大问题

问题 在哪节回答
Item 的 x/y/width/height 在 Scene Graph 中对应什么? §5.2
anchors.left: parent.left 背后到底做了什么计算? §5.4
Positioners 和 Layouts 该选哪个?性能差多少? §5.5 + §5.6

# 5.2 Item 基类

# 5.2.1 几何属性

所有 QML 可视元素的共同祖先都是 Item(C++ 侧 = QQuickItem):

Item {
    x: 10; y: 20              // 相对父元素的坐标
    width: 100; height: 50    // 尺寸
    z: 3                      // 堆叠顺序(越大越靠前,默认 0)

    // 变换属性
    rotation: 45              // 旋转角度
    scale: 1.5                // 缩放(1.0 = 原大小)
    transformOrigin: Item.Center  // 变换原点

    // 隐式尺寸(由内容决定,优先于显式 width/height)
    implicitWidth: 200
    implicitHeight: 100
}

几何属性的底层映射:

Item { x: 10; y: 20; width: 100; height: 50 }
  ↓ QQmlEngine 创建
QQuickItem* item = new QQuickItem();
item->setX(10);
item->setY(20);
item->setWidth(100);
item->setHeight(50);
  ↓ Sync 阶段
QSGTransformNode* transformNode = new QSGTransformNode();
transformNode->setMatrix(QMatrix4x4(...));  // 由 x/y/rotation/scale 合成

implicitWidth/Height 的作用——当你不显式指定尺寸时,引擎用隐式尺寸作为回退:

// Text 的 implicitWidth = 文本渲染后的像素宽度
Text {
    text: "Hello World"           // implicitWidth ≈ 110(取决于字体和大小)
    // 外部没写 width → 引擎用 implicitWidth
}

// 组合使用
Item {
    width: Math.max(100, child.implicitWidth)  // 至少 100,但内容多时跟着宽
    Rectangle { id: child; implicitWidth: 80; implicitHeight: 40 }
}

# 5.2.2 可见裁剪

属性 作用 Scene Graph 影响 性能代价
visible: false 不渲染、不占布局空间 QSGNode 被移除出渲染树 无渲染开销,但仍占用对象树
opacity: 0.5 半透明渲染 强制创建 QSGOpacityNode → 打断批处理 高(触发独立 batch)
clip: true 裁剪超出边界的子元素 创建 QSGClipNode → scissor test 高(每个 clip 一个独立 batch)
enabled: false 不响应事件 不影响渲染树 无渲染开销
// ❌ 误区:visible: false 不等于"删除对象"
Rectangle {
    id: heavyPanel
    visible: false          // 对象仍存在!绑定仍在求值!只是不画
    width: complexCalculation()  // ← 还在算,浪费 CPU
}

// ✅ 按需创建/销毁
Loader {
    active: needPanel       // false = 彻底销毁子对象树
    sourceComponent: heavyPanelComponent
}

# 5.2.3 SG 映射

每个 Item 在 Scene Graph 中映射为一个 QSGTransformNode:

QML 对象树                       Scene Graph 渲染树

Item (id: root)                   QSGRootNode ←─ 根
  ├─ Rectangle (x:10, y:20)        QSGTransformNode (translate(10,20))
  │    └─ ...                          ├─ QSGGeometryNode (顶点+材质)
  │                                    └─ ...
  ├─ Item (rotation: 45)           QSGTransformNode (rotate(45°))
  │    └─ ...                          └─ ...
  └─ MouseArea                      ← 无视觉,不进入渲染树

关键:QML 的 x/y/rotation/scale 不是直接作用在 QSGGeometryNode 上——而是通过一个夹层 QSGTransformNode 套在子节点树上。这就是为什么同一父级 Item 下的子元素可以共享变换。


# 5.3 基础元素

# 5.3.1 四种元素

Rectangle —— 最基础的绘制单元:

Rectangle {
    width: 200; height: 100
    color: "steelblue"          // 纯色填充
    gradient: Gradient {        // 渐变(与 color 二选一)
        GradientStop { position: 0; color: "white" }
        GradientStop { position: 1; color: "black" }
    }
    border { width: 2; color: "#333" }  // 边框
    radius: 12                  // 圆角(0 = 直角)
}

Text —— 文本渲染:

Text {
    text: "Hello QML"
    font { family: "Arial"; pixelSize: 24; bold: true }
    color: "#333"
    horizontalAlignment: Text.AlignHCenter
    elide: Text.ElideRight          // 超出宽度时加省略号
    wrapMode: Text.WordWrap         // 自动换行
    lineHeight: 1.5
}

Image —— 图片加载与缩放:

Image {
    source: "qrc:/images/logo.png"        // qrc 资源
    // source: "file:///usr/share/bg.jpg"  // 本地文件
    // source: "https://cdn.example.com/..."  // 网络图片(异步)
    fillMode: Image.PreserveAspectFit     // 等比缩放 | PreserveAspectCrop | Stretch | Tile
    asynchronous: true                    // ⚡ 异步加载,不阻塞 GUI
    cache: true                           // 启用像素缓存
    mipmap: true                          // 缩小图片时生成 mipmap(抗锯齿)
}

TextInput —— 单行文本输入:

TextInput {
    width: 200
    text: "initial"
    font.pixelSize: 16
    maximumLength: 20
    validator: IntValidator { bottom: 0; top: 100 }
    echoMode: TextInput.Password          // 密码模式
    onAccepted: console.log("回车确认:", text)
}

# 5.3.2 底层节点

QML 元素 C++ 类 Scene Graph 节点 主要开销
Rectangle QQuickRectangle QSGGeometryNode + QSGFlatColorMaterial 4 个顶点,1 个三角形 strip
Text QQuickText QSGGeometryNode + 纹理材质 每个字符若干顶点
Image QQuickImage QSGGeometryNode + QSGTextureMaterial 1 次纹理绑定
TextInput QQuickTextInput QSGGeometryNode + 纹理(光标+选中范围额外节点) 文本 + 光标矩形
Item QQuickItem 仅 QSGTransformNode(无绘制) 仅变换矩阵

# 5.4 锚布局原理

# 5.4.1 约束求解

写 anchors.left: parent.left 时,QML 引擎不是简单设 x = 0——它在内部建立了一个约束方程组:

Rectangle {
    id: child
    anchors {
        left: parent.left;   leftMargin: 10     // 约束①: child.x = parent.x + 10
        right: parent.right; rightMargin: 10    // 约束②: child.x + child.width = parent.x + parent.width - 10
        top: parent.top;     topMargin: 10      // 约束③: child.y = parent.y + 10
    }
    height: 50                                   // 约束④: child.height = 50
}

引擎求解步骤:

① + ② → child.x 和 child.width 被唯一确定:
  child.width = (parent.width - 10 - 10) - 0   = parent.width - 20
  child.x     = parent.x + 10

③ + ④ → child.y 被唯一确定:
  child.y     = parent.y + 10

约束求解发生在:

  • QML 对象首次创建时(QQmlComponent::create())
  • 任何锚点依赖的属性变化时(如 parent.width 改变)
  • 渲染 Sync 阶段之前(确保本帧渲染用的是最新布局)

# 5.4.2 优先级冲突

当多个约束同时作用于同一个属性时:

Rectangle {
    id: child
    anchors {
        left: parent.left
        right: parent.right         // left + right 同时设 → 推导 width
        horizontalCenter: parent.horizontalCenter  // ← 与 left+right 冲突!
    }
}
// → QML 引擎打印 warning: "Conflicting anchors"
// → 实际结果:horizontalCenter 被忽略,left+right 生效

解析优先级(引擎内部遍历顺序):

优先级 约束组 确定属性
1 left + right x + width(两个方程联立)
2 horizontalCenter x(一个方程)
3 top + bottom y + height
4 verticalCenter y
5 baseline y(与 Text 基线对齐)
6 margins 偏移量

铁律:anchors.fill: parent = left+right+top+bottom 四锚全绑 → x/y/width/height 全部由约束确定,显式写的 width: 100 会被覆盖。

# 5.4.3 锚点对比

维度 anchors x/y 绝对坐标
父元素尺寸变化时 ✅ 自动跟随 ❌ 不跟随(硬编码)
性能 有约束求解开销(~µs 级) 零开销
可读性 高(声明关系) 低(魔法数字)
适合场景 需要自适应布局 固定尺寸、Canvas 内部绘制
// 场景:需要自适应 → anchors
Rectangle {
    anchors { fill: parent; margins: 10 }
}

// 场景:Canvas 内部的固定像素绘制 → 绝对坐标
Canvas {
    onPaint: {
        var ctx = getContext("2d")
        ctx.fillRect(50, 50, 100, 100)     // 绝对坐标,OK
    }
}

# 5.4.4 锚布局案例

用一个登录页面演示 anchors 的完整约束链:

import QtQuick
import QtQuick.Controls

ApplicationWindow {
    width: 400; height: 600; visible: true

    Column {
        anchors.centerIn: parent
        spacing: 16

        // Logo
        Rectangle {
            width: 80; height: 80; radius: 40; color: "#2196F3"
            anchors.horizontalCenter: parent.horizontalCenter
        }

        // 标题
        Text {
            text: "欢迎登录"; font.pixelSize: 24; font.bold: true
            anchors.horizontalCenter: parent.horizontalCenter
        }

        // 输入框组
        Column {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 8

            TextField {
                width: 280; placeholderText: "用户名"
            }
            TextField {
                width: 280; placeholderText: "密码"; echoMode: TextInput.Password
            }
        }

        // 按钮
        Button {
            text: "登录"; width: 280
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

这个布局在 720p → 4K 上一直居中,零代码修改。


# 5.5 定位器

# 5.5.1 四种定位器

Positioners 是轻量级的排列容器——仅做位置计算,不参与布局协商:

// Row:水平排列
Row {
    spacing: 8
    Rectangle { width: 50; height: 50; color: "red" }
    Rectangle { width: 50; height: 50; color: "green" }
    Rectangle { width: 50; height: 50; color: "blue" }
}

// Column:垂直排列
Column {
    spacing: 8
    Text { text: "Item 1" }
    Text { text: "Item 2" }
    Text { text: "Item 3" }
}

// Grid:网格排列
Grid {
    columns: 3; spacing: 8
    Repeater {
        model: 9
        Rectangle { width: 50; height: 50; color: "steelblue" }
    }
}

// Flow:自动换行排列(类似 CSS flow layout)
Flow {
    width: 200; spacing: 8
    Repeater {
        model: 10
        Rectangle { width: 60; height: 30; color: "orange" }
    }
}

Positioners 的底层实现:

Row { spacing: 8; Repeater { ... } }
  ↓
QQmlComponent 创建 Row 对象(QQuickPositioner 子类)
  ↓
componentComplete() 阶段:
  遍历所有子元素 → 按顺序计算每个子元素的 x/y
  child[0].x = 0
  child[1].x = child[0].width + 8
  child[2].x = child[1].x + child[1].width + 8
  ...

Positioners 做的是一次性位置计算——子元素尺寸变化时重新计算,但不做动态的"谁该占多少空间"的协商。

# 5.5.2 复杂度控制

Positioners 在嵌入式上有天然优势——零额外对象树开销:

容器 额外 QObject 数 适用场景
Row / Column 1 个 固定数量子元素、简单排列
Grid 1 个 网格排布
Layout 系列 每个子元素 +1 (LayoutAttached) 需要动态协商尺寸 / 权重分配

# 5.6 布局管理器

# 5.6.1 三种布局

Layouts 比 Positioners 多一个核心能力:子元素可以声明自己想要的尺寸偏好:

import QtQuick.Layouts

RowLayout {
    spacing: 8

    Rectangle {
        Layout.fillWidth: true      // 尽量占满剩余宽度
        Layout.preferredWidth: 100  // 但理想宽度 100
        color: "red"; height: 50
    }

    Rectangle {
        Layout.fillWidth: false
        Layout.preferredWidth: 80
        color: "green"; height: 50
    }

    Rectangle {
        Layout.fillWidth: true
        Layout.minimumWidth: 50
        color: "blue"; height: 50
    }
}

Layout 附加属性:

属性 含义 默认值
Layout.fillWidth 填满剩余宽度 false
Layout.fillHeight 填满剩余高度 false
Layout.preferredWidth 理想宽度(有足够空间时) implicitWidth
Layout.minimumWidth 最小宽度(不可压缩至更小) 0
Layout.maximumWidth 最大宽度(不可超出) Infinity
Layout.alignment 在分配空间内的对齐 Qt.AlignLeft | Qt.AlignVCenter
Layout.row / Layout.column GridLayout 中的行列索引 按声明顺序

Layout 的协商过程(以 RowLayout 为例):

Step 1: 收集——遍历所有子元素,收集每个 Layout.preferredWidth
Step 2: 分配——总宽度 = parent.width - spacing × (count - 1) - margins
         按优先级: minimumWidth → preferredWidth → fillWidth 比例分配剩余
Step 3: 应用——给每个子元素 setWidth(calculated),触发 Scene Graph 更新

# 5.6.2 选择策略

场景 推荐 理由
固定数量子元素,尺寸确定 Row / Column 零额外开销
需要子元素自动填满空间 RowLayout / ColumnLayout Layout 协商机制
简单网格排列 Grid 轻量
复杂网格(带跨行、跨列) GridLayout Layout.row/column 支持
子元素数量动态变化 Layouts Layout 更灵活
嵌入式高分屏 ListView delegate 内 Row / Column 对象树开销最小

# 5.7 多屏仪表盘

用本章的布局技术实现一套适配 800×480 到 1920×720 的仪表盘:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

ApplicationWindow {
    width: 800; height: 480; visible: true
    property real speed: 80; property real rpm: 3000

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

        // ───── 顶部:速度(自适应宽度) ─────
        RowLayout {
            Layout.fillWidth: true
            spacing: 12

            Rectangle {
                Layout.fillWidth: true; Layout.preferredHeight: 120
                radius: 12; color: "#1a1a2e"
                Text {
                    anchors.centerIn: parent
                    text: speed.toFixed(0) + " km/h"
                    font.pixelSize: parent.height * 0.35
                    color: speed > 120 ? "red" : "white"
                }
            }

            Rectangle {
                Layout.fillWidth: true; Layout.preferredHeight: 120
                radius: 12; color: "#1a1a2e"
                Text {
                    anchors.centerIn: parent
                    text: "RPM " + rpm.toFixed(0)
                    font.pixelSize: parent.height * 0.25
                    color: rpm > 5000 ? "orange" : "white"
                }
            }
        }

        // ───── 中间:地图区域(填满剩余空间) ─────
        Rectangle {
            Layout.fillWidth: true; Layout.fillHeight: true
            radius: 12; color: "#2d2d44"
            Text {
                anchors.centerIn: parent
                text: "地图区域"; color: "#888"
            }
        }

        // ───── 底部:状态栏 ─────
        Row {
            Layout.fillWidth: true
            spacing: 8
            Rectangle { width: parent.width/4; height: 40; radius: 8; color: "#16213e"
                Text { anchors.centerIn: parent; text: "油量 85%"; color: "white" } }
            Rectangle { width: parent.width/4; height: 40; radius: 8; color: "#16213e"
                Text { anchors.centerIn: parent; text: "里程 12.3k"; color: "white" } }
            Rectangle { width: parent.width/4; height: 40; radius: 8; color: "#16213e"
                Text { anchors.centerIn: parent; text: "温度 26°C"; color: "white" } }
            Rectangle { width: parent.width/4; height: 40; radius: 8; color: "#16213e"
                Text { anchors.centerIn: parent; text: "连接 OK"; color: "white" } }
        }
    }
}

案例知识融合:本案例集成了本章全部概念——①外层 ColumnLayout 纵向分割(顶部速度 / 中地图 / 底部状态栏);②RowLayout 让两个速度面板水平平分宽度;③内层 Row 在底部状态栏按 parent.width / 4 四等分(Row 不做协商,直接用父宽度算);④所有尺寸以父元素为参考,无硬编码——整段代码无缝适应 720p / 1080p / 4K。


# 5.8 新手陷阱

# 陷阱 说明 修复
1 anchors.fill 后仍显式写 width width: 100 会被 anchors 覆盖——无声失效 二选一:要么 anchors 控制尺寸,要么显式写 width/height
2 clip: true 滥用 每个 clip 创建一个独立 QSGClipNode → 打断批处理 仅在内容真的溢出时才加 clip
3 Layout 的 fillWidth 和 preferredWidth 混淆 fillWidth: true + preferredWidth 是"先分 preferred,再分剩余"——不是"占比" 用 Layout.preferredWidth 设初始值、Layout.fillWidth 决定是否抢剩余
4 Positioner 内 Item 不设置尺寸 Row/Column 不会给子元素分配宽高——没有显式 width 的子元素可能被隐式设为 0 子元素必须设 size
5 z 值不是全局排序 z 只在同级 Item 之间比较——不同父元素下的 z 互不影响 需要跨父级的层级用 Item 扁平化或 parent 重分配

# 5.9 训练题

训练题:锚点约束冲突修复

以下 QML 有布局冲突。找出问题并修复:

Rectangle {
    width: 400; height: 400

    Rectangle {
        id: box
        width: 100
        anchors {
            left: parent.left
            right: parent.right
            horizontalCenter: parent.horizontalCenter   // ← 冲突!
            top: parent.top
        }
        height: 50
        color: "steelblue"
    }
}
答案

anchors.left + anchors.right 已经同时确定了 box 的 x 和 width。再加 horizontalCenter 产生冲突。修复:去掉 horizontalCenter: parent.horizontalCenter(或去掉 left+right,仅保留 horizontalCenter + width: 100)。


# 5.10 速查表

概念 一句话
Item 所有可视元素的基类,x/y/width/height/z/rotation/scale
implicitWidth 由内容决定的自然宽度——不显式写 width 时使用
anchors 约束式布局:left/right/top/bottom/centerIn/fill/margins
Positioners 轻量排列容器(Row/Column/Grid/Flow)——一次性位置计算
Layouts 动态协商容器(RowLayout/ColumnLayout/GridLayout)——子元素可声明 size 偏好
clip 裁剪超出边界的内容——代价是独立 QSGClipNode,打断批处理
z 同级 Item 的堆叠顺序——越大越靠前
Layout.fillWidth 声明"我要填满剩余宽度"
Layout.preferredWidth 声明"有足够空间时我理想是这么宽"

核心哲学:

QML 的布局系统不是"又一套 CSS"——
它是基于 QQuickItem 属性系统 + Scene Graph 变换链的
声明式约束求解引擎。

用好 anchors 和 Layouts = 一套代码适配所有分辨率。

下一篇:06. 事件处理与传播机制 (opens new window)

上次更新: 2026/07/12, 19:39:51
属性绑定与响应式原理
事件处理与传播机制

← 属性绑定与响应式原理 事件处理与传播机制→

最近更新
01
audit
07-27
02
C++入门教程全章思考题汇编
07-24
03
12.技术团队建设能力
07-21
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式