适合人群: 已理解 QML 基础语法,想掌握 Qt Quick 核心视觉元素的开发者
前言
上一篇我们系统学习了 QML 的语法机制——对象、属性、绑定、信号。本篇进入 Qt Quick 模块本身:它提供了哪些视觉元素,如何导入外部资源,如何处理用户输入,以及如何用 JavaScript 扩展交互逻辑。
Qt Quick 是建立在 QML 语言之上的标准组件库,是你构建实际界面的直接工具。
一、搭建项目基础
本文所有示例基于以下 CMakeLists.txt 配置:
cmake_minimum_required(VERSION 3.16)
project(QtQuickDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick)
qt_standard_project_setup(REQUIRES 6.5)
qt_add_executable(QtQuickDemo main.cpp)
qt_add_qml_module(QtQuickDemo
URI QtQuickDemo
VERSION 1.0
QML_FILES Main.qml
RESOURCES
images/background.jpg
fonts/MyFont.ttf
)
target_link_libraries(QtQuickDemo PRIVATE Qt6::Quick)
main.cpp 使用标准模板不作修改,所有开发工作集中在 QML 文件中。
二、核心视觉元素
2.1 Item — 所有视觉元素的基类
Item 是 Qt Quick 中所有可视元素的基类,它本身不可见,但提供了所有视觉元素共有的属性:
import QtQuick
Item {
width: 400
height: 300
// Item 的核心属性
x: 0; y: 0 // 位置
z: 0 // 层叠顺序,数值大的在上层
opacity: 1.0 // 透明度 0.0 ~ 1.0
visible: true // 是否显示
clip: false // 是否裁剪超出边界的子元素
rotation: 0 // 旋转角度(度)
scale: 1.0 // 缩放比例
}
2.2 Rectangle — 矩形
最常用的容器元素:
import QtQuick
Rectangle {
width: 200
height: 120
color: "#4A90E2"
radius: 12 // 圆角
// 边框
border.width: 2
border.color: "#2C5F9E"
// 渐变色
gradient: Gradient {
GradientStop { position: 0.0; color: "#6AB0F5" }
GradientStop { position: 1.0; color: "#2C72C7" }
}
}
2.3 Text — 文本
import QtQuick
Text {
text: "Qt Quick 文本示例"
// 字体设置
font.family: "Arial"
font.pixelSize: 18
font.bold: true
font.italic: false
font.letterSpacing: 1.5 // 字间距
// 颜色与对齐
color: "#333333"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
// 多行处理
width: 300
wrapMode: Text.WordWrap // 自动换行
elide: Text.ElideRight // 超出显示省略号
// 富文本
textFormat: Text.RichText
text: "普通文字 <b>加粗</b> <i>斜体</i> <font color='red'>红色</font>"
}
2.4 Image — 图片
import QtQuick
Image {
width: 200
height: 200
source: "images/photo.jpg" // 相对路径
// 缩放模式
fillMode: Image.PreserveAspectFit // 保持比例,完整显示
// fillMode: Image.PreserveAspectCrop // 保持比例,裁剪填满
// fillMode: Image.Stretch // 拉伸填满(可能变形)
// 加载状态
onStatusChanged: {
if (status === Image.Ready)
console.log("图片加载完成")
else if (status === Image.Error)
console.log("图片加载失败")
}
}
加载网络图片:
Image {
source: "https://example.com/image.jpg"
// 网络图片加载是异步的,status 会经历 Loading → Ready
}
三、导入外部资源
3.1 使用自定义字体
第一步: 在 CMakeLists.txt 的 RESOURCES 中注册字体文件。
第二步: 在 QML 中加载字体:
import QtQuick
Item {
// 加载自定义字体
FontLoader {
id: customFont
source: "fonts/MyFont.ttf"
}
Text {
text: "自定义字体效果"
font.family: customFont.name // 使用加载的字体
font.pixelSize: 24
}
}
3.2 使用图片资源
注册到 CMakeLists.txt 后,在 QML 中直接用相对路径引用:
// 项目内资源(推荐)
Image { source: "images/logo.png" }
// 也可以用 qrc:/ 前缀显式引用
Image { source: "qrc:/QtQuickDemo/images/logo.png" }
四、定位:anchors 锚点系统
anchors 是 Qt Quick 中最灵活的定位机制,通过将一个元素的边与另一个元素的边对齐来定位。
4.1 基本锚点
Rectangle {
id: parent_rect
width: 400; height: 300
// 贴左边
Rectangle {
width: 100; height: 100
color: "red"
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 10 // 所有方向留 10px 间距
}
// 贴右下角
Rectangle {
width: 100; height: 100
color: "blue"
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 10
anchors.bottomMargin: 10
}
// 水平居中,垂直方向在顶部
Rectangle {
width: 100; height: 40
color: "green"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
}
}
4.2 填满父容器
Rectangle {
anchors.fill: parent // 完全填满父容器
anchors.margins: 16 // 四周留 16px 边距
}
4.3 相对于兄弟元素定位
Rectangle {
id: firstBox
width: 100; height: 100
color: "orange"
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 20
}
Rectangle {
width: 100; height: 100
color: "purple"
anchors.left: firstBox.right // 紧跟在 firstBox 右边
anchors.leftMargin: 10
anchors.top: firstBox.top // 与 firstBox 顶部对齐
}
五、处理用户输入
5.1 MouseArea — 鼠标与触控
import QtQuick
Rectangle {
width: 200
height: 100
color: clickArea.pressed ? "#2C72C7" : "#4A90E2" // 按下时变深色
radius: 8
MouseArea {
id: clickArea
anchors.fill: parent
// 常用信号
onClicked: console.log("点击,位置:" + mouse.x + "," + mouse.y)
onDoubleClicked: console.log("双击")
onPressAndHold: console.log("长按")
onEntered: console.log("鼠标进入")
onExited: console.log("鼠标离开")
// 接受右键
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton)
console.log("右键点击")
}
}
Text {
anchors.centerIn: parent
text: clickArea.pressed ? "按住中..." : "点击我"
color: "white"
font.pixelSize: 16
}
}
5.2 鼠标悬停效果
Rectangle {
id: card
width: 180; height: 100
radius: 10
color: "#f5f5f5"
border.width: 1
border.color: hoverArea.containsMouse ? "#4A90E2" : "#e0e0e0"
scale: hoverArea.containsMouse ? 1.03 : 1.0 // 悬停时轻微放大
// scale 属性变化自动有过渡效果(需配合 Behavior,见后续课程)
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true // 必须启用才能检测 containsMouse
}
Text {
anchors.centerIn: parent
text: "悬停查看效果"
color: hoverArea.containsMouse ? "#4A90E2" : "#666"
font.pixelSize: 14
}
}
六、使用 JavaScript 扩展逻辑
QML 原生支持 JavaScript,可以直接在属性绑定和信号处理器中写逻辑,也可以定义函数。
6.1 内联 JavaScript
import QtQuick
Rectangle {
width: 300; height: 200
property int score: 75
Text {
anchors.centerIn: parent
font.pixelSize: 18
// 三元表达式
text: score >= 90 ? "优秀"
: score >= 75 ? "良好"
: score >= 60 ? "及格"
: "不及格"
color: score >= 75 ? "#1D9E75" : "#E24A4A"
}
}
6.2 定义函数
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 360; height: 300
visible: true
property real celsius: 0
// 在 Item 内定义函数
function celsiusToFahrenheit(c) {
return (c * 9 / 5 + 32).toFixed(1)
}
function getTemperatureLabel(c) {
if (c < 0) return "冰点以下"
if (c < 15) return "寒冷"
if (c < 25) return "舒适"
if (c < 35) return "温热"
return "炎热"
}
Column {
anchors.centerIn: parent
spacing: 16
width: 280
Slider {
width: parent.width
from: -20; to: 50; value: 0
onValueChanged: celsius = value
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: celsius.toFixed(1) + "°C = " + celsiusToFahrenheit(celsius) + "°F"
font.pixelSize: 22
font.bold: true
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: getTemperatureLabel(celsius)
font.pixelSize: 16
color: "#888"
}
}
}
6.3 将 JS 逻辑抽离到独立文件
当 JavaScript 逻辑较复杂时,可以放入独立的 .js 文件:
新建 utils.js:
// utils.js
.pragma library // 声明为共享库,多个 QML 文件导入时只加载一次
function formatNumber(num) {
return num.toLocaleString()
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value))
}
在 QML 中导入使用:
import "utils.js" as Utils
Text {
text: Utils.formatNumber(1234567) // 输出:1,234,567
}
七、创建自定义组件
7.1 提取独立组件文件
把一个"卡片"封装成可复用的组件 InfoCard.qml:
// InfoCard.qml
import QtQuick
Rectangle {
id: root
// 对外暴露的属性接口
property string title: "标题"
property string subtitle: "副标题"
property color accentColor: "#4A90E2"
// 对外暴露的信号
signal tapped()
width: 240
height: 90
radius: 12
color: "#ffffff"
border.width: 1
border.color: "#e8e8e8"
// 左侧色条
Rectangle {
width: 4
height: parent.height
radius: 2
color: root.accentColor
}
Column {
anchors {
left: parent.left
leftMargin: 20
verticalCenter: parent.verticalCenter
}
spacing: 4
Text {
text: root.title
font.pixelSize: 16
font.bold: true
color: "#222"
}
Text {
text: root.subtitle
font.pixelSize: 13
color: "#888"
}
}
MouseArea {
anchors.fill: parent
onClicked: root.tapped()
}
}
在主文件中使用:
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 360; height: 400
visible: true
Column {
anchors.centerIn: parent
spacing: 12
InfoCard {
title: "今日步数"
subtitle: "8,432 步"
accentColor: "#1D9E75"
onTapped: console.log("点击了步数卡片")
}
InfoCard {
title: "活跃时间"
subtitle: "47 分钟"
accentColor: "#4A90E2"
onTapped: console.log("点击了时间卡片")
}
InfoCard {
title: "消耗热量"
subtitle: "312 千卡"
accentColor: "#E2934A"
onTapped: console.log("点击了热量卡片")
}
}
}
7.2 组件的属性别名(alias)
alias 让外部可以直接访问组件内部某个子元素的属性:
// SearchBar.qml
import QtQuick
import QtQuick.Controls
Rectangle {
id: root
height: 44
radius: 22
color: "#f5f5f5"
border.width: 1
border.color: "#e0e0e0"
// alias 将内部 textField 的 text 属性暴露出去
property alias searchText: textField.text
property alias placeholderText: textField.placeholderText
signal searchSubmitted(string query)
TextField {
id: textField
anchors {
left: parent.left
right: submitBtn.left
verticalCenter: parent.verticalCenter
leftMargin: 16; rightMargin: 8
}
background: Item {} // 去掉默认背景
placeholderText: "搜索..."
onAccepted: root.searchSubmitted(text)
}
Button {
id: submitBtn
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: 8
}
text: "搜索"
flat: true
onClicked: root.searchSubmitted(textField.text)
}
}
使用时直接访问 searchText:
SearchBar {
id: bar
width: 300
onSearchSubmitted: function(query) {
console.log("搜索:" + query)
}
}
Text {
text: "当前输入:" + bar.searchText // 通过 alias 访问内部属性
}
八、综合示例:个人资料卡片
整合本文所有知识点,构建一个完整的个人资料展示组件:
// ProfileCard.qml
import QtQuick
import QtQuick.Controls
Rectangle {
id: root
property string avatarSource: ""
property string name: "用户名"
property string bio: "个人简介"
property int followerCount: 0
property int followingCount: 0
signal followClicked()
width: 320
height: 200
radius: 16
color: "#ffffff"
border.width: 0.5
border.color: "#e8e8e8"
// 顶部背景条
Rectangle {
width: parent.width
height: 70
color: "#4A90E2"
radius: 16
// 修复底部圆角
Rectangle {
width: parent.width
height: 16
anchors.bottom: parent.bottom
color: parent.color
}
}
// 头像
Rectangle {
id: avatarFrame
width: 64; height: 64
radius: 32
color: "#e0e0e0"
border.width: 3
border.color: "white"
anchors {
left: parent.left
leftMargin: 20
top: parent.top
topMargin: 38
}
Image {
anchors.fill: parent
anchors.margins: 2
source: root.avatarSource
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: null
}
// 无头像时显示首字母
Text {
anchors.centerIn: parent
text: root.name.length > 0 ? root.name[0].toUpperCase() : "?"
font.pixelSize: 24
font.bold: true
color: "#888"
visible: root.avatarSource === ""
}
}
// 关注按钮
Button {
text: "关注"
anchors {
right: parent.right
rightMargin: 16
top: parent.top
topMargin: 80
}
onClicked: root.followClicked()
}
// 名字和简介
Column {
anchors {
left: parent.left
leftMargin: 20
top: avatarFrame.bottom
topMargin: 8
}
spacing: 2
Text {
text: root.name
font.pixelSize: 16
font.bold: true
color: "#222"
}
Text {
text: root.bio
font.pixelSize: 13
color: "#888"
width: 200
elide: Text.ElideRight
}
}
// 粉丝数据
Row {
anchors {
right: parent.right
rightMargin: 20
bottom: parent.bottom
bottomMargin: 14
}
spacing: 16
Column {
horizontalItemAlignment: Qt.AlignHCenter
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: root.followerCount
font.pixelSize: 15
font.bold: true
color: "#222"
}
Text {
text: "粉丝"
font.pixelSize: 11
color: "#aaa"
}
}
Column {
horizontalItemAlignment: Qt.AlignHCenter
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: root.followingCount
font.pixelSize: 15
font.bold: true
color: "#222"
}
Text {
text: "关注"
font.pixelSize: 11
color: "#aaa"
}
}
}
}
在主文件中使用:
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 400; height: 300
visible: true
ProfileCard {
anchors.centerIn: parent
name: "林小明"
bio: "Qt 开发爱好者 · 嵌入式工程师"
followerCount: 1248
followingCount: 362
onFollowClicked: console.log("点击关注")
}
}
九、下一步
掌握了 Qt Quick 的核心视觉元素之后,建议继续:
- Introduction to Qt Quick Controls — 学习更完整的 UI 控件库(按钮、输入框、菜单、对话框等)
- Positioners and Layouts — 深入布局系统,让 UI 自适应不同屏幕尺寸
- QML Fluid Elements and Animation — 为界面加入流畅动画
总结
| 元素 / 概念 | 用途 |
|---|---|
Item | 所有视觉元素的基类,提供位置、透明度、旋转等基础属性 |
Rectangle | 矩形容器,支持圆角、边框、渐变 |
Text | 文本显示,支持富文本、换行、省略 |
Image | 图片显示,支持多种缩放模式和异步加载 |
FontLoader | 加载自定义字体文件 |
anchors | 锚点定位系统,通过边对齐实现灵活布局 |
MouseArea | 处理鼠标和触控事件 |
| JavaScript 函数 | 在 QML 中定义逻辑函数,复杂逻辑可抽离到 .js 文件 |
| 自定义组件 | 独立 .qml 文件封装,property 暴露接口,alias 透传内部属性 |