核心必備模塊
開始QML編程所需的最小Qt 6模塊集。
Qt Core - 其他模塊使用的核心非圖形類。
Qt GUI - 圖形用戶界面(GUI)組件的基類。包括OpenGL。
Qt Network - 使網(wǎng)絡(luò)編程更簡單,更便攜的類。
Qt QML - 用于QML和JavaScript語言的類。
Qt Quick - 一種聲明式框架,用于構(gòu)建具有自定義用戶界面的高度動態(tài)應(yīng)用程序。
Qt Quick Controls - 提供輕量級QML類型,用于為桌面,嵌入式和移動設(shè)備創(chuàng)建高性能用戶界面。這些類型采用簡單的樣式架構(gòu),效率非常高。
Qt Quick Layouts - 布局是用于在用戶界面中排列基于Qt Quick 2的項目的項目。
Qt Widgets - 用C++小部件擴展Qt GUI的類。
Qt D-BUS - 用于在linux上通過D-Bus協(xié)議進行進程間通信的類。
Qt Quick Test - QML應(yīng)用程序的單元測試框架,其中測試用例以JavaScript函數(shù)的形式編寫。
Qt Test - 用于對Qt應(yīng)用程序和庫進行單元測試的類。

Qt Quick 是 Qt 6 中用于用戶界面技術(shù)的總稱。它在 Qt 4 中引入,現(xiàn)在擴展到 Qt 6。Qt Quick 本身是幾種技術(shù)的集合:
? QML - 用戶界面的標(biāo)記語言
? JavaScript - 動態(tài)腳本語言
? Qt C++ - 高度可移植的增強型 c++ 庫。

在典型的項目中,前端是用 QML/JavaScript 開發(fā)的。與系統(tǒng)接口并進行繁重工作的后端代碼是使用 Qt C++ 開發(fā)的。
旋轉(zhuǎn)風(fēng)車示例


======>Step?. 組裝風(fēng)車
import QtQuick
Item {
id: root
width: background.width
height: background.height
Image {
id: background
source: "images/background.png"
}
Image {
id: pole
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
source: "images/pole.png"
}
Image {
id: pinwheel
anchors.centerIn: parent
source: "images/pinwheel.png"
}
}
代碼解讀:
每個類型都有系列屬性(properties),對于QML類型Image,它有width、height、source等屬性。
通過官方文檔:Qt Quick QML Types,我們查看一下QML類型Image的屬性列表,如下:

可以看到,它繼承自根QML類型Item,我們用到的width、height屬性就是從Item繼承而來的。
絕大部分的標(biāo)準(zhǔn)QML類型都坐落在QtQuick模塊,所以我們在使用時導(dǎo)入此模塊即可:import QtQuick,不加版本號即代表當(dāng)前環(huán)境的最新版本。
id 是一個特殊的可選屬性,它包含一個標(biāo)識符,可以用來在文檔的其他地方引用其關(guān)聯(lián)類型。重要提示:id 屬性在設(shè)置后不能更改,也不能在運行時設(shè)置。
要將風(fēng)車放在中間,我們使用一種復(fù)雜的屬性,稱為錨點(anchor)。錨定允許您指定父對象和兄弟對象之間的幾何關(guān)系。QML提供了一種靈活的方法來使用錨點布局項目。錨定的概念是Item的基礎(chǔ),并且對所有視覺QML元素都可用。錨點就像一份合同(contract),比競爭的幾何變化更強大。錨點是相對性的表達;您總是需要一個相關(guān)元素來錨定。

有時您可能希望進行小的調(diào)整,例如,將類型稍微偏離中心。這可以通過anchors.horizontalCenterOffset或anchors.verticalCenterOffset來完成。類似的調(diào)整屬性也適用于所有其他錨點。錨點屬性的完整列表,如下:

您按照圖層和分組的順序描述用戶界面的視覺外觀,其中最上層(我們的背景圖像)首先繪制,子圖層在其上方繪制在包含類型的本地坐標(biāo)系中。即風(fēng)輪遮擋桿子,桿子遮擋背景。默認(rèn)z屬性值為0,z是real類型,即它是實數(shù),所以 取值是z∈(-∞,+∞),z值越大就在越上層。這里風(fēng)輪 、桿子、背景的z值都是默認(rèn)值0(即相等)。但在代碼結(jié)構(gòu)里面,它們的放置順序決定了先放置QML類型先繪制,后放置的后繪制,這也就產(chǎn)生了堆疊層次效果。
======>Step?. 風(fēng)輪旋轉(zhuǎn)
Image {
id: root
...
MouseArea {
anchors.fill: parent
onClicked: wheel.rotation += 45
}
...
}
代碼解讀:
我們使用MouseArea類型并使其覆蓋根類型的整個區(qū)域。鼠標(biāo)區(qū)域在用戶單擊其覆蓋區(qū)域內(nèi)時發(fā)出信號(signals)。您可以通過重寫 onClicked 函數(shù)來連接此信號。當(dāng)信號連接時,意味著每當(dāng)發(fā)出信號時,都會調(diào)用它對應(yīng)的函數(shù)(或函數(shù)組)。在這種情況下,我們說當(dāng)鼠標(biāo)區(qū)域中有鼠標(biāo)單擊時,id為wheel的類型(即風(fēng)輪圖像)應(yīng)該旋轉(zhuǎn)+45度。
這種技術(shù)適用于每個信號(signal),命名約定為on + SignalName。此外,所有屬性在其值更改(value changed)時都會發(fā)出信號。對于這些信號,命名約定是:on${property}Changed。例如,如果更改了width屬性,則可以使用onWidthChanged: print(width)來觀察它。
======>Step?. 風(fēng)輪流暢旋轉(zhuǎn)
上面,當(dāng)用戶單擊時,輪子會旋轉(zhuǎn),但旋轉(zhuǎn)是一次性完成的,而不是隨著時間流動而流暢地運動。
Image {
id: root
Image {
id: wheel
Behavior on rotation {
NumberAnimation {
duration: 125
}
}
}
}
代碼解讀:
我們可以使用動畫來實現(xiàn)平滑運動。動畫定義了屬性如何在一段時間內(nèi)發(fā)生變化。行為(Behavior)為定義的屬性指定了每次對該屬性(這里是rotation)應(yīng)用更改時的動畫。換句話說,每當(dāng)屬性發(fā)生變化時,都會運行動畫。這只是在QML中進行動畫的眾多方法之一。Behavior使用格式:Behavior on ${property}。
現(xiàn)在,每當(dāng)輪子的旋轉(zhuǎn)屬性發(fā)生變化時,它都會使用持續(xù)時間為125毫秒的NumberAnimation進行動畫處理。因此,每個45度的轉(zhuǎn)彎都將花費125毫秒,產(chǎn)生一個漂亮流暢的旋轉(zhuǎn)。
請注意,一個屬性不能有多個分配的行為。要在行為中提供多個動畫,請使用ParallelAnimation或SequentialAnimation。
如果狀態(tài)更改(state change)具有與行為(Behavior)匹配相同屬性的過渡(Transition),則過渡動畫(Transition animation)將覆蓋該狀態(tài)更改(state change)的行為(Behavior)。后面文章會有介紹,或參見官方文檔: Behavior example。
下面截圖來自:Animation and Transitions in Qt Quick,展示了 動畫(Animation) 和 過渡(Transitions) 的所有類型:

======>Step?. 風(fēng)輪旋轉(zhuǎn)視覺模糊效果

Image {
id: blur
opacity: 0// 完全透明(默認(rèn)值為1.0)
anchors.centerIn: parent
source: "images/blur.png"
Behavior on rotation {
NumberAnimation { duration: 125 }
}
Behavior on opacity {
NumberAnimation { duration: 125 }
}
}
======>Step?. 控制風(fēng)車的順向和逆向旋轉(zhuǎn)
Item {
...
focus: true
Keys.onLeftPressed: {
blur.opacity = 1
pinwheel.rotation -= root.rotationStep
blur.rotation -= root.rotationStep
}
Keys.onRightPressed: {
blur.opacity = 0.5
pinwheel.rotation += root.rotationStep
blur.rotation += root.rotationStep
}
Keys.onReleased: {
blur.opacity = 0
}
}
前文有講到,對于每個信號(signal),按命名約定為on + SignalName,通過重寫 onSignalName 函數(shù)來連接此信號。所有屬性在其值更改時都會發(fā)出信號。當(dāng)信號連接時,意味著每當(dāng)發(fā)出信號時,都會調(diào)用它對應(yīng)的函數(shù)(或函數(shù)組)。
我們通過觸發(fā)鍵盤按鍵來發(fā)送信號,以完成對風(fēng)車的控制。先來看看QML類型Keys有哪些信號,如下圖所求:

從上圖可以看到,所有Keys信號函數(shù)都具有一個名為 event 的KeyEvent類型參數(shù),其中包含事件的詳細(xì)信息。如果按鍵被處理,則應(yīng)將event.accepted設(shè)置為true,以防止事件在項目層次結(jié)構(gòu)中傳播。
上圖中的黃色線標(biāo)明的兩個信號,根據(jù)命名規(guī)則,連接這兩信號對應(yīng)的要重寫的函數(shù)分別為:onLeftPressed和onRightPressed。
/** 如果帶參數(shù),寫法如下:*/
Keys.onPressed: function(event) {
if (event.key == Qt.Key_Left) {
console.log("move left");
event.accepted = true;
}
}
/* 或:*/
Key.onPressed: (event)=> {
if (event.key == Qt.Key_Right) {...}
}
/** 如果用不到參數(shù),寫法如下:*/
Keys.onPressed: { ... }
我們看到,有句代碼很重要:focus: true,它表明Item將獲取活動的焦點,這樣才能捕獲鍵盤事件,否則按鍵對風(fēng)車不起作用。
按下鍵盤的左方向鍵時,風(fēng)車逆向旋轉(zhuǎn)45°,模糊風(fēng)輪圖像從完全透明變?yōu)橥耆煌该?,逐漸覆蓋正常的風(fēng)輪,即表明旋轉(zhuǎn)速度變快,然后模糊風(fēng)輪圖像總是opacity=1,保持覆蓋狀態(tài),形成正常人類眼睛視覺殘留效果。當(dāng)按下右方向鍵時,風(fēng)車順向旋轉(zhuǎn)45°,模糊風(fēng)輪圖像從完全透明變?yōu)榘胪该?,逐漸疊加到正常的風(fēng)輪,也表明旋轉(zhuǎn)速度變快,然后模糊風(fēng)輪圖像總是opacity=0.5,保持疊加狀態(tài),順風(fēng)方向并沒有嚴(yán)重殘影。松開按鍵,重置opacity=0。
最終的效果圖及完整QML代碼如下:

import QtQuick
Item {
id: root
width: background.width
height: background.height
property int rotationStep: 45
Image {
id: background
source: "images/background.png"
}
Image {
id: pole
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
source: "images/pole.png"
}
Image {
id: pinwheel
anchors.centerIn: parent
source: "images/pinwheel.png"
Behavior on rotation {
NumberAnimation { duration: 125 }
}
}
Image {
id: blur
opacity: 0
anchors.centerIn: parent
source: "images/blur.png"
Behavior on rotation {
NumberAnimation { duration: 125 }
}
Behavior on opacity {
NumberAnimation { duration: 125 }
}
}
/********************************/
focus: true
Keys.onLeftPressed: {
blur.opacity = 1
pinwheel.rotation -= root.rotationStep
blur.rotation -= root.rotationStep
}
Keys.onRightPressed: {
blur.opacity = 0.5
pinwheel.rotation += root.rotationStep
blur.rotation += root.rotationStep
}
Keys.onReleased: {
blur.opacity = 0
}
}