4. QML语言入门:QML语法基础与基本元素

好,咱们开始聊QML。说实话,我第一次接触QML的时候,心里是有点抵触的——又学一门新语言?但用了一周之后,我彻底改观了。QML的声明式语法,说白了就是「你想要什么,就直接写什么」,特别适合做车载界面这种需要频繁迭代的场景。

4.1 QML语法基础

QML的语法结构其实很简单。一个QML文件就是一个对象树,每个对象都有属性、信号和绑定。我习惯把QML理解成「带逻辑的JSON」——结构清晰,但能跑起来。

4.1.1 属性(Property)

属性就是对象的特征。比如一个矩形的宽、高、颜色。写法很直接:

Rectangle {
    width: 200
    height: 100
    color: "blue"
    radius: 10
}

嗯,这里要注意:属性名是大小写敏感的。我在项目中遇到过有人把color写成Color,结果界面死活不显示颜色——找了半天才发现是大小写问题。

属性还可以是表达式。比如:

Rectangle {
    width: parent.width * 0.5
    height: parent.height * 0.3
}

你想想看,这种写法多方便。父容器一变,子元素自动跟着调整。这就是QML的「绑定」机制在起作用。

4.1.2 信号(Signal)

信号是QML里的事件机制。比如按钮被点击、滑块被拖动,都会发射信号。我刚开始学的时候,总觉得信号和槽的概念很抽象。后来我告诉自己:信号就是「喊一声」,槽就是「听到后干活」。

Button {
    text: "点击我"
    onClicked: {
        console.log("按钮被点击了")
        // 这里可以写更多逻辑
    }
}

我曾经在车载项目中犯过一个低级错误:信号处理函数里写了耗时的操作,结果界面卡住了。后来才意识到,信号处理应该尽量轻量,复杂逻辑要放到后台去。

4.1.3 绑定(Binding)

绑定是QML最强大的特性之一。它让属性之间自动保持同步。比如:

Rectangle {
    width: 200
    height: 100
    color: mouseArea.pressed ? "red" : "blue"
    
    MouseArea {
        id: mouseArea
        anchors.fill: parent
    }
}

这里color绑定了mouseArea.pressed的状态。鼠标按下时变红,松开后变蓝。不需要写任何更新代码——绑定自动搞定。

我的小技巧: 绑定表达式里尽量用简单的逻辑。如果表达式太复杂,可以考虑用Qt.binding()函数显式创建绑定,这样代码更清晰,也更容易调试。

4.2 基本元素

QML里最常用的三个基本元素就是Rectangle、Text和Image。别看它们简单,车载界面里80%的UI都是靠它们拼出来的。

4.2.1 Rectangle(矩形)

Rectangle是QML的「万能积木」。它可以画背景、做边框、当容器。我习惯用它来做所有需要「框起来」的东西。

Rectangle {
    width: 300
    height: 200
    color: "#2C3E50"
    border.color: "#3498DB"
    border.width: 2
    radius: 8
    
    // 渐变效果
    gradient: Gradient {
        GradientStop { position: 0.0; color: "#2C3E50" }
        GradientStop { position: 1.0; color: "#34495E" }
    }
}

嗯,这里有个坑:radius属性只对纯色背景有效。如果你用了渐变或者图片背景,圆角可能不会生效。我曾经在这个问题上折腾了一下午。

4.2.2 Text(文本)

Text元素用来显示文字。车载界面对文字的要求很高——字体大小、颜色、对齐方式都要精确控制。

Text {
    text: "当前车速: 80 km/h"
    font.family: "Arial"
    font.pixelSize: 24
    font.bold: true
    color: "#ECF0F1"
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter
}

我建议在车载项目里,字体大小至少用pixelSize而不是pointSize。因为不同屏幕的DPI不一样,pointSize可能会导致文字忽大忽小。

避坑指南: 我曾经在车载项目中直接用系统默认字体,结果在部分设备上中文显示为方框。后来我养成了习惯——所有Text元素都显式指定支持中文的字体,比如"Noto Sans SC"或"微软雅黑"。

4.2.3 Image(图片)

Image用来显示图片。车载界面里,图标、背景图、按钮图片都靠它。

Image {
    source: "qrc:/images/speedometer.png"
    width: 100
    height: 100
    fillMode: Image.PreserveAspectFit
    smooth: true
}

注意fillMode属性。我见过很多新手直接用默认的Stretch模式,结果图片被拉伸得面目全非。车载屏幕上,图标比例一定要保持,所以我一般用PreserveAspectFit

4.3 布局管理器

布局管理器帮你自动排列子元素。手动计算坐标?那是上古时代的做法了。

4.3.1 Row(行布局)

Row把子元素从左到右排成一行。适合做导航栏、按钮组。

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

说白了,Row就是「排排坐,吃果果」。每个元素之间的间距由spacing控制。

4.3.2 Column(列布局)

Column和Row相反,从上到下排列。适合做列表、菜单。

Column {
    spacing: 5
    anchors.centerIn: parent
    
    Text { text: "设置"; font.pixelSize: 18 }
    Text { text: "导航"; font.pixelSize: 18 }
    Text { text: "音乐"; font.pixelSize: 18 }
}

我习惯在Column外面包一层Rectangle,这样整个列表看起来有背景,更美观。

4.3.3 Grid(网格布局)

Grid把元素排成网格。适合做图标矩阵、功能面板。

Grid {
    columns: 3
    spacing: 10
    anchors.centerIn: parent
    
    Rectangle { width: 80; height: 80; color: "#E74C3C" }
    Rectangle { width: 80; height: 80; color: "#3498DB" }
    Rectangle { width: 80; height: 80; color: "#2ECC71" }
    Rectangle { width: 80; height: 80; color: "#F39C12" }
    Rectangle { width: 80; height: 80; color: "#9B59B6" }
    Rectangle { width: 80; height: 80; color: "#1ABC9C" }
}

你想想看,如果手动计算每个方块的位置,得写多少代码?Grid一行搞定。

4.3.4 StackView(堆叠视图)

StackView是QML里做页面切换的利器。它像一叠卡片,每次只显示最上面那张。车载界面里,从主菜单切换到设置页面,用StackView最合适。

StackView {
    id: stackView
    anchors.fill: parent
    initialItem: mainPage
    
    Component {
        id: mainPage
        Rectangle {
            color: "#2C3E50"
            Text {
                text: "主页面"
                anchors.centerIn: parent
                color: "white"
                font.pixelSize: 24
            }
        }
    }
    
    Component {
        id: settingsPage
        Rectangle {
            color: "#34495E"
            Text {
                text: "设置页面"
                anchors.centerIn: parent
                color: "white"
                font.pixelSize: 24
            }
        }
    }
}

// 切换页面
Button {
    text: "进入设置"
    onClicked: stackView.push(settingsPage)
}

嗯,这里要注意:pushpop操作要成对使用。我曾经在项目中连续push了5个页面,结果用户按返回键时,界面跳得飞快——因为StackView里堆了太多页面。后来我加了个限制,最多保留3层。

核心总结: QML的布局管理器让界面开发变得像搭积木一样简单。Row、Column、Grid负责「怎么排列」,StackView负责「怎么切换」。掌握了这四种布局,车载界面里90%的布局需求都能搞定。

好了,这一章的内容就到这里。下一章我们会深入QML的动画系统——让界面动起来,才是车载娱乐系统的灵魂。