4. Linux输入子系统与设备节点

这一章我们聊聊 QML 和 Qt Quick 的布局体系。说实话,很多做 Qt 的朋友一开始都被 QML 的「声明式」写法搞懵过。我当年第一次接触 QML 时,心里想的是:这玩意儿不就是写 JSON 吗?后来踩了几个坑才明白,它的设计哲学和传统 Widget 完全是两码事。

QML 布局的核心,说白了就是「描述你想要什么,而不是怎么实现」。你告诉它「这个按钮在父容器右下角」,它自己会算坐标。嗯,这里的关键是——你得学会用正确的方式描述。

4.1 QML 基础语法:声明式思维

QML 文件的基本单元是对象,每个对象由类型名和大括号组成。属性用 key: value 的格式写,子对象直接嵌套。

// 一个简单的 QML 对象
Rectangle {
    width: 200
    height: 100
    color: "lightblue"

    Text {
        text: "Hello Qt Quick"
        anchors.centerIn: parent
    }
}

我个人习惯把属性分组排列:尺寸放第一行,颜色第二,布局属性第三。这样别人看代码时一眼就能扫到关键信息。

小技巧:QML 属性名是大小写敏感的。width 和 Width 是两个东西。我见过有人因为手误把 anchors.centerIn 写成 anchors.centrein,结果控件死活不居中,查了半天。

4.2 Item 与 Rectangle:一切的基础

Item 是所有可视元素的基类。它本身不绘制任何东西,但可以容纳子对象、处理事件、做布局容器。Rectangle 继承自 Item,多了颜色和边框绘制能力。

特性 Item Rectangle
是否可见 不可见(透明容器) 可见(有颜色/边框)
常用场景 布局容器、事件区域 背景、按钮、卡片
性能开销 极低 低(纯色填充时)
子对象裁剪 默认不裁剪 可设置 clip: true

我在项目中经常用 Item 做「虚拟容器」——只负责布局,不产生任何绘制开销。你想想看,如果每个容器都用 Rectangle,哪怕设成透明,渲染器也得处理它。用 Item 就完全没这个问题。

注意:Rectangle 的 radius 属性(圆角)会触发离屏渲染。如果列表中有大量圆角矩形,性能会明显下降。我曾经在一个聊天列表里用了 50 个圆角头像框,结果滑动掉帧严重。后来改成用图片遮罩,帧率直接翻倍。

4.3 锚点布局:最灵活的定位方式

锚点布局(anchors)是 QML 最强大的布局机制之一。它通过「锚定」一个元素的边到另一个元素的边来实现相对定位。

Rectangle {
    id: parentBox
    width: 300; height: 200
    color: "#eee"

    Rectangle {
        id: childBox
        width: 100; height: 50
        color: "#3498db"

        // 锚定到父容器右下角,并留 10px 边距
        anchors.right: parentBox.right
        anchors.bottom: parentBox.bottom
        anchors.margins: 10
    }
}

锚点有 7 个锚定点:left、right、top、bottom、horizontalCenter、verticalCenter、baseline。你可以组合使用,比如「左上角对齐父容器左上角,右边延伸到父容器水平居中位置」——这种混合锚定在实际布局中非常常见。

核心原则:锚点布局不要形成循环依赖。A 锚到 B,B 又锚到 A,QML 引擎会报错。我调试时遇到过这种问题,排查了半天才发现是两个兄弟元素互相锚定了。

锚点布局的常见模式:

  • 居中:anchors.centerIn: parent —— 最常用的一行代码
  • 填满:anchors.fill: parent —— 子元素完全覆盖父容器
  • 边距:anchors.margins: 10 —— 统一设置四边边距
  • 偏移:anchors.horizontalCenterOffset: 5 —— 微调位置

4.4 Row / Column / Grid:自动排列布局

当你有多个子元素需要按行、列或网格排列时,手动写锚点就太累了。这时候用 Row、Column、Grid 就对了。

Row:水平排列

Row {
    spacing: 10
    Rectangle { width: 50; height: 50; color: "red" }
    Rectangle { width: 50; height: 50; color: "green" }
    Rectangle { width: 50; height: 50; color: "blue" }
}

spacing 控制元素间距。注意:Row 不会自动换行,如果总宽度超出父容器,子元素会被裁剪或溢出。

Column:垂直排列

Column {
    spacing: 8
    anchors.centerIn: parent
    Button { text: "确定" }
    Button { text: "取消" }
    Button { text: "重置" }
}

Column 和 Row 的 API 几乎对称。我习惯把按钮组、表单字段用 Column 包起来,配合 anchors.centerIn 快速居中。

Grid:网格排列

Grid {
    columns: 3
    rows: 2
    spacing: 5
    // 子元素按行优先填充
    Rectangle { width: 30; height: 30; color: "#e74c3c" }
    Rectangle { width: 30; height: 30; color: "#2ecc71" }
    Rectangle { width: 30; height: 30; color: "#3498db" }
    Rectangle { width: 30; height: 30; color: "#f39c12" }
    Rectangle { width: 30; height: 30; color: "#9b59b6" }
    Rectangle { width: 30; height: 30; color: "#1abc9c" }
}

Grid 默认按行填充(先填满第一行,再第二行)。如果想按列填充,设置 flow: Grid.TopToBottom。

性能提示:Row、Column、Grid 内部会计算每个子元素的位置。如果子元素数量超过 100 个,建议改用 ListView 或 GridView,它们有虚拟化机制,只渲染可见区域。我在一个仪表盘项目里用 Grid 放了 200 个小方块,结果初始化卡了 2 秒——换成 GridView 后瞬间加载。

4.5 布局体系知识图谱

下面这张图总结了 QML 布局的核心分类和选择逻辑。你可以把它当作决策参考:

QML 布局体系决策树 布局需求 手动定位 自动排列 x/y 坐标 锚点 anchors Row / Column Grid 选择建议: • 2-5 个元素固定排列 → 锚点布局(最灵活) • 多个元素按行/列排列 → Row / Column(代码简洁) • 大量元素或动态数据 → ListView / GridView(虚拟化)

4.6 布局嵌套与性能考量

实际项目中很少只用一种布局。常见做法是:外层用锚点定位大区块,内部用 Row/Column/Grid 排列子元素。

// 混合布局示例:顶部标题 + 中间网格 + 底部按钮
Item {
    width: 400; height: 300

    // 顶部标题
    Text {
        id: title
        anchors.top: parent.top
        anchors.horizontalCenter: parent.horizontalCenter
        text: "功能选择"
        font.pixelSize: 20
    }

    // 中间网格
    Grid {
        id: gridArea
        anchors.top: title.bottom
        anchors.topMargin: 20
        anchors.horizontalCenter: parent.horizontalCenter
        columns: 3
        spacing: 10
        // ... 网格子元素
    }

    // 底部按钮
    Row {
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 15
        anchors.horizontalCenter: parent.horizontalCenter
        spacing: 20
        Button { text: "取消" }
        Button { text: "确认" }
    }
}
避坑指南:我曾经在一个深层嵌套的布局里,同时用了 anchors.fill、anchors.centerIn 和 anchors.margins,结果子元素的位置完全不对。后来发现是锚点冲突——一个元素不能同时被 fill 和 centerIn 约束。记住:每个方向(水平和垂直)只能有一个锚定规则。

布局嵌套深度建议控制在 5 层以内。每多一层,QML 引擎就要多一次布局计算。如果你发现界面切换时有明显的「闪一下」或「跳一下」,多半是布局计算耗时过长。

嗯,这一章的内容就到这里。QML 布局的灵活性很高,但越灵活的东西越容易用错。我的建议是:先掌握锚点布局和 Row/Column/Grid 这几种基础方式,遇到复杂场景时再考虑 Layout 附加属性或自定义布局。别一上来就搞花活——简单的东西往往最可靠。