簡介
本文是《Qml組件化編程》系列文章的第一篇,濤哥將教大家,如何在Qml中實現各種功能的按鈕,
同時也會教大家一些組件化編程的思想,如何將做好的功能封裝成一個個組件,以便在工程中複用。
文章主要發佈在濤哥的博客 和 知乎專欄-濤哥的Qt進階之路。
寫作背景
作者“武威的濤哥”,從2015年開始參加工作,便入手了Qml,參與了很多大大小小的Qml項目,
至今已有四年多實戰經驗。
2019年,濤哥決定把自己掌握的很多知識都總結整理出來,以《Qml組件化編程》系列文章的形式分享給廣大Qml
愛好者和開發者。
濤哥會堅持高質量和深入淺出的原則,將文章寫好,讓支持濤哥的讀者能夠受益匪淺。
系列文章中涉及的源代碼,絕大部分濤哥都會在github上開源。
如果覺得濤哥寫的還不錯,還請爲濤哥打個賞,您的讚賞是濤哥持續創作的源泉。
有時也難免會犯一些錯誤,希望看到的讀者能夠熱心指出。有任何相關的問題,也歡迎與濤
哥交流,向濤哥提出建議和意見。
感謝大家!
文章定位
濤哥寫的是進階教程,教大家如何從新手成長爲高手,要理解文章中的內容,需要有一點兒Qml基礎。
關於基礎教程,網絡上有很多,濤哥推薦以下幾個質量比較高的:
-
豆子的系列文章《Qt學習之路》 76章開始 《Qt學習之路》
豆子的文章前75章關於Widget部分也很不錯。
- 還有一本安曉輝的書《QtQuick核心編程》
我眼中的QQuick
QQuick使用Qml來描述界面,Qml是可以與html5媲美的存在,其開發效率、舒適度、描述能力和定製化能力已經遠遠超
過了QWidget,配合各種屬性動畫、粒子系統和Effects特效可以輕易做出各種酷炫、現代化的UI,不規則的圖形可以通
過Painter的方式實現,對於渲染有性能要求的地方可以通過集成OpenGL、Vulkan等圖形API的方式來處理。
Qml的特性是自由和靈活,這也是它的缺點,上手Qml需要一小段時間的適應,之後就會大量的造輪子,造的多了就輕車
熟路了,常見的各種二維界面或效果基本上都能造出來。
(當然Qml中也有些bug,需要一定的經驗和技巧才能解決。話說回來,哪個框架沒點Bug呢?)
再久一點,可以考慮考慮Qml中的設計模式(或者叫慣用手法),如何抽象出通用Qml庫、如何最大程度地複用Qml
代碼、如何讓Qml代碼更容易維護等。
Qt版本的選擇
Qt發佈的版本有很多,一般穩妥的做法是使用LTS(長期支持版)版本的最後一個修正版本。
當前已知的LTS最新修正版本是5.9.8和5.12.3 , 濤哥會優先使用這兩個版本,後續有版本
要求的地方,濤哥都會進行額外的說明。
默認按鈕
開始進入正題了
Qml中已經有了現成的按鈕,在QtQuick.Controls模塊中, 目前有兩個版本:
- QtQuick.Controls 1.x
1.x的版本是通過style的方式進行定製
- QtQuick.Controls 2.x
2.x的版本則是通過修改Control屬性的方式進行定製
2.x版本的默認風格,還可以通過修改配置文件qtquickcontrols2.conf中的style來修改
Qml版本混用
這裏順便說一個經常有人問到的問題: 2.x版本中能否混用1.x版本的控件?
答案是可以。只需要使用別名機制即可。(如果你看過Qml的源代碼,就能輕易發現這個用法)
// MixControls.qml
import QtQuick 2.0
import QtQuick.Controls 2.0 //導入Controls 2.0模塊
import QtQuick.Controls 1.4 as QC14 //導入Controls 1.4模塊,取別名QC14
import QtQuick.Controls.Styles 1.4 as QCS14 //導入Controls.Styles 1.4模塊,取別名QCS14
Rectange {
Button { //默認使用2.0的Button
}
QC14.Button { //通過別名使用1.4的Button
style: QCS14.ButtonStyle { //通過別名使用1.4的style
}
}
}
按鈕的本質
默認的按鈕,很多時候並不能達到想要的效果,比如圓角、貼圖片、漸變色的按鈕等等,還需要做很多的定製化工作
其實按鈕的本質,就是一個可以點擊的區域(MouseArea),附帶顏色(Rectangle)、圖片(Image)或文字(Text)。
濤哥教大家造第一個輪子,一個帶文字的按鈕。
// Taobutton.qml
import QtQuick 2.0
import QtQuick.Controls 2.0
Item {
width: 800
height: 600
property color btnColor: "#009688"
Rectangle {
width: 140
height: 40
anchors.centerIn: parent
color: btnArea.pressed ? Qt.darker(btnColor, 1.2) : (btnArea.containsMouse ? Qt.lighter(btnColor, 1.2) : btnColor)
Text {
id: btnText
anchors.centerIn: parent
text: qsTr("我是一個按鈕")
}
MouseArea {
id: btnArea
anchors.fill: parent
hoverEnabled: true //打開懸浮
onClicked: {
console.log(qsTr("按鈕被按了"))
}
}
}
}
使用qmlscene預覽一下效果
(gif錄製工具不能鼠標懸浮,所以懸浮效果看不到,實際上是有懸浮效果的,可自行嘗試)
說明一下,qml中有個全局的對象Qt,它有一組調顏色的函數,Qt.lighter和Qt.darker,分別可以按係數來變淺和加深
一個顏色值,這樣只要有了一個顏色值,就能自動計算出深一點或者淺一點的顏色,可以減少很多配顏色的工作哦
再看一下這個表達式,當鼠標按下去的時候,使用深一點的顏色,鼠標懸浮的時候使用淺一點的顏色,其它情況就用設定的顏色
color: btnArea.pressed ? Qt.darker(btnColor, 1.2) : (btnArea.containsMouse ? Qt.lighter(btnColor, 1.2) : btnColor)
這裏的1.2是試出來的值,也可以使用其它值。
按鈕的演變
如果要帶個邊框呢?只要給Rectange設置邊框的寬度和顏色就行了
property color btnBorderColor: "orange"
Rectangle {
...
border.width: btnArea.containsMouse ? 2 : 0 //鼠標懸浮時有寬度,才能看到邊框
border.color: btnBorderColor
...
}
如果要圓角呢? 只要給Rectangle 設置radius就行了
Rectangle{
...
radius: 5
...
}
如果要圓形呢?只要給Rectangle設置寬度和高度相等(正方形),radius是寬度的一半即可
Rectangle{
...
width: 120
height: width
radius: width / 2
...
}
如果要背景色做成漸變的呢?
Rectangle{
id: gradientBtn
...
property color btnColor: containsMouse ? Qt.darker("#009688") : "#009688"
color: btnColor
gradient: Gradient {
GradientStop { position: 0 ; color: Qt.darker(gradientBtn.btnColor, 1.2) }
GradientStop { position: 0.5 ; color: Qt.darker(gradientBtn.btnColor, 1.4) }
GradientStop { position: 1 ; color: Qt.darker(gradientBtn.btnColor, 1.6) }
}
...
}
順帶提一下,5.12的Rectangle,有了新的漸變色 漸變色網站,大約有180種
可以直接在Qml中使用。
gradient: Gradient.NightFade //通過枚舉使用
gradient: "SugarLollipop" //通過字符串名字使用
(gif錄製工具不能鼠標懸浮,所以懸浮效果看不到,實際上是有懸浮效果的,可自行嘗試)
組件化按鈕
爲了能夠複用我們的按鈕,需要將它做成一個組件。
這裏以文本按鈕爲例:
// TTextBtn.qml
import QtQuick 2.9
import QtQuick.Controls 2.0
Rectangle {
id: root
property alias textItem: t //導出Text實例,方便外部直接修改
property alias text: t.text //導出文本
property alias textColor: t.color //導出文本顏色
property alias containsMouse: area.containsMouse //導出鼠標懸浮
property alias containsPress: area.containsPress //導出鼠標按下
signal clicked(); //自定義點擊信號
color: "transparent"
Text {
id: t
//默認座標居中
anchors.centerIn: parent
//默認文字對齊方式爲水平和垂直居中
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//默認寬度爲parent的寬度,這樣字太長超出範圍時自動顯示省略號
width: parent.width
}
MouseArea {
id: area
anchors.fill: parent;
hoverEnabled: parent.enabled;
onClicked: root.clicked(); //點擊時觸發自定義點擊信號
cursorShape: Qt.PointingHandCursor //懸浮或點擊時的鼠標樣式
}
}
Qml組件,先以最簡單的方式理解,就是放在一個單獨的Qml文件中,聲明一些屬性導出,由使用者去實例化並設置屬性
比如這樣:
爲了和標準的組件區分開,濤哥寫的組件名字都以大寫的T開頭。
組件化的好處,包括容易複用(如上圖,多個實例都不一樣)、可以統一修改(後面會有動態換皮膚的方案,依賴於組件化)、
便於維護和擴展等。後續濤哥還會講如何做多層抽象的組件、單例組件、如何引用插件中的組件等。
組件化圖片按鈕
帶圖片的按鈕,只需要把Rectangle換成Image即可。
爲了應對各種按鈕狀態,濤哥做了以下的屬性擴展
// TImageBtn.qml
import QtQuick 2.9
import QtQuick.Controls 2.0
Item {
id: root
property url normalUrl //常規狀態下的圖片路徑
property url hoveredUrl //懸浮
property url pressedUrl //按下
property url disabledUrl //禁用
property alias imageItem: img //直接別名導出Image實例,外面可以修改其任意屬性
property alias imageUrl: img.source //別名導出圖片路徑
property alias imageWidth: img.width
property alias imageHeight: img.height
property alias imageAnchors: img.anchors
property alias containsMouse: area.containsMouse
property alias containsPress: area.containsPress
//點擊信號
signal clicked();
Image {
id: img
anchors.fill: parent
//默認按鼠標狀態選取不同的圖片
source: root.enabled ? (containsPress ? pressedUrl : (containsMouse ? hoveredUrl : normalUrl)) : disabledUrl
}
MouseArea {
id: area
anchors.fill: parent;
hoverEnabled: parent.enabled;
onClicked: root.clicked();
cursorShape: Qt.PointingHandCursor
preventStealing: true
}
}
組件化圖文按鈕
有了前面的文字按鈕和圖片按鈕,我們可以做一個圖片和文字都有的按鈕。
圖片和文字同時顯示,那麼就有了佈局問題。圖片在左?還是在右?在上?還是在下?
濤哥這裏用了點技巧,封裝了一個同時支持四種佈局的圖文按鈕
// TImgTextBtn.qml
import QtQuick 2.9
import QtQuick.Controls 2.0
Rectangle {
id: root
property url normalUrl
property url hoveredUrl
property url pressedUrl
property url disabledUrl
property alias imageItem: img
property alias imageUrl: img.source
property alias imageWidth: img.width
property alias imageHeight: img.height
property alias textItem: t
property alias text: t.text
property alias textColor: t.color
property alias containsMouse: area.containsMouse
property alias containsPress: area.containsPress
signal clicked();
//5.10以前的版本,Qml中沒有枚舉,用int屬性代替枚舉
property int layoutType: layoutImageLeft //佈局類型,默認圖片在左,外部可修改
readonly property int layoutImageLeft: 0 //圖片在左 只讀屬性,代替枚舉
readonly property int layoutImageRight: 1 //圖片在右 只讀屬性,代替枚舉
readonly property int layoutImageUp: 2 //圖片在上 只讀屬性,代替枚舉
readonly property int layoutImageDown: 3 //圖片在下 只讀屬性,代替枚舉
color: "transparent"
Image {
id: img
source: root.enabled ? (containsPress ? pressedUrl : (containsMouse ? hoveredUrl : normalUrl)) : disabledUrl
}
Text {
id: t
//默認文字對齊方式爲水平和垂直居中
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
//按佈局類型 處理佈局
Component.onCompleted: {
switch (layoutType) {
case layoutImageLeft:
img.anchors.verticalCenter = root.verticalCenter
t.anchors.verticalCenter = root.verticalCenter
img.anchors.left = root.left
t.anchors.left = img.right
t.anchors.leftMargin = 6
break;
case layoutImageRight:
img.anchors.verticalCenter = root.verticalCenter
t.anchors.verticalCenter = root.verticalCenter
t.anchors.left = root.left
img.anchors.left = t.right
img.anchors.leftMargin = 6
break
case layoutImageUp:
img.anchors.horizontalCenter = root.horizontalCenter
t.anchors.horizontalCenter = root.horizontalCenter
img.anchors.top = root.top
t.anchors.top = img.bottom
t.anchors.topMargin = 6
break
case layoutImageDown:
img.anchors.horizontalCenter = root.horizontalCenter
t.anchors.horizontalCenter = root.horizontalCenter
t.anchors.top = root.top
img.anchors.top = t.bottom
img.anchors.topMargin = 6
break;
}
}
MouseArea {
id: area
anchors.fill: parent;
hoverEnabled: parent.enabled;
onClicked: root.clicked();
cursorShape: Qt.PointingHandCursor
}
}
最後,我們來看看效果吧
轉載聲明
文章出自濤哥的博客
文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作 © 濤哥
聯繫方式
作者 | 濤哥 |
---|---|
開發理念 | 弘揚魯班文化,傳承工匠精神 |
博客 | https://jaredtao.github.io |
知乎 | https://www.zhihu.com/people/wentao-jia |
郵箱 | [email protected] |
微信 | xsd2410421 |
759378563 |
請放心聯繫我,樂於提供諮詢服務,也可洽談商務合作相關事宜。
打賞
如果覺得濤哥寫的還不錯,還請爲濤哥打個賞,您的讚賞是濤哥持續創作的源泉。