簡介
本文是《Qml組件化編程》系列文章的第三篇,濤哥將教大家,如何在Qml中實現動態換皮膚。順帶會分享一些Qt小技巧。
文章主要發佈在濤哥的博客 和 知乎專欄-濤哥的Qt進階之路。
效果預覽
效果類似於網易雲音樂
順便說一下,這是濤哥創建的TaoQuick項目,後續的各種組件、效果會全部集中在這個項目裏。
文章中涉及的代碼,都會先貼出來。整個工程的代碼,在積累到一定程度後,會開放在github上。
必要的基礎
可能有讀者會疑惑,濤哥前兩篇文章還在講如何封裝基礎組件,這第三篇直接就來換皮膚?是不是跨度有點大?
其實換皮膚是一個很基礎的功能,如果要做最好在項目初期就做起來,後期想要做換皮膚會困難一些(工作量大)。
如果你的項目做了很多組件化的封裝,再做換皮膚會輕鬆一些。
QObject自定義屬性
Qml中有一個類型叫QtObject,濤哥非常喜歡使用這個類型。
以前在寫Qt/C++代碼中的自定義QObject時,經常需要寫一些自定義的Q_PROPERTY,以及實現set、get函數、change信號
如果純手工寫,挺累人的,濤哥曾寫過自動生成器,相信很多人也寫過類似的工具。
後來濤哥發現,QtCreator有自動生成的功能,只要寫上Q_PROPERTY那一行,再用右鍵菜單生成即可,
高效率人士也可以使用快捷鍵,光標放在Q_PROPERTY上,按Alt + Enter。
有時候需要把set函數的參數改成const T &類型來減少內存拷貝,並把函數實現移動到cpp文件中。(這都是C++的詬病)
然而,在Qml中,有更加方便的QtObject,
Item {
QtObject { //定義一個QtObject,相當於Qt/c++代碼中的QObject
id: dataObj
property string name: "Hello" //這就定義了一個string類型的屬性name,初始值設爲"Hello"
//定義完了,已經自帶了onNameChanged信號。name被重新賦值時會觸發信號
//給這個信號寫一個處理函數,就相當於連接上了槽函數。
onNameChanged: {
console.info("name:", name)
}
}
...
Button {
...
onClicked: { //演示: 按鈕按下時,修改前面QtObject的name屬性
dataObj.name = "World";
}
}
...
}
(哈哈,工作量和心理負擔一下子減輕了很多,頭髮的數量也能保住了。再也不想回去寫Qt/C++的屬性了。)
全局單例
濤哥寫了一個單獨的qml文件,頂層就是一個QtObject類型,裏面會有一大堆屬性。
顏色、字體一類的配置都在這裏。
// GlobalConfig.qml
import QtQuick 2.0
QtObject {
property color titleBackground: "#c62f2f" //標題欄的背景色
property color background: "#f6f6f6" //標題欄之外的部分的背景色
property color reserverColor: "#ffffff" //與背景色相反的對比色
property color textColor: "black" //文本顏色
}
然後在main.qml中實例化它
// main.qml
Item {
width: 800
height: 600
GlobalConfig {
id: gConfig
}
...
}
Qml有個特性,子頁面實例可以通過id訪問父頁面中的實例,讀 寫其屬性、調用其函數。
在main.qml中實例化的對象,相當於是全局的了,它的id是可以在所有main.qml的子頁面中訪問到的。
(當然還有一種方式,通過qmldir指定單例,這種留着後面再說)
實現
皮膚的配置和原理
下面是TaoQuick中使用的gConfig
// GlobalConfig.qml
QtObject {
property color titleBackground: "#c62f2f"
property color background: "#f6f6f6"
property color reserverColor: "#ffffff"
property color textColor: "black"
property color splitColor: "gray"
property int currentTheme: 0
onCurrentThemeChanged: {
var t = themes.get(currentTheme)
titleBackground = t.titleBackground
background = t.background
textColor = t.textColor
}
readonly property ListModel themes: ListModel {
ListElement {
name: qsTr("一品紅")
titleBackground: "#c62f2f"
background: "#f6f6f6"
textColor: "#5c5c5c"
}
ListElement {
name: qsTr("高冷黑")
titleBackground: "#191b1f"
background: "#222225"
textColor: "#adafb2"
}
ListElement {
name: qsTr("淑女粉")
titleBackground: "#faa0c5"
background: "#f6f6f6"
textColor: "#5c5c5c"
}
ListElement {
name: qsTr("富貴金")
titleBackground: "#fed98f"
background: "#f6f6f6"
textColor: "#5c5c5c"
}
ListElement {
name: qsTr(" 清爽綠")
titleBackground: "#58c979"
background: "#f6f6f6"
textColor: "#5c5c5c"
}
ListElement {
name: qsTr("蒼穹藍")
titleBackground: "#67c1fd"
background: "#f6f6f6"
textColor: "#5c5c5c"
}
}
}
濤哥在所有的Page頁面中,相關顏色設置都綁定到gConfig的相應屬性上。
那麼換皮膚,只需要修改gConfig中的顏色相關屬性即可。因爲修改屬性時會觸發change信號,而所有的Page都綁定了
gConfig的屬性,會自動在發生change時重新讀屬性,修改後的顏色自動就生效了。
這裏順帶說一下,主題的顏色相關屬性越少越好,因爲太多了不容易識別、不好維護,
中提到的Qt.lighter和Qt.darker就是一種減少顏色屬性數量的神器。
皮膚選擇器
再來看一下,濤哥參考 網易雲音樂 做的皮膚選擇器
TImageBtn { //圖片按鈕,參考文章1
width: 20
height: 20
anchors.verticalCenter: parent.verticalCenter
imageUrl: containsMouse ? "qrc:/Image/Window/skin_white.png" : "qrc:/Image/Window/skin_gray.png"
onClicked: {
skinBox.show()
}
TPopup { //自定義的彈窗,帶三角尖尖的那個。
id: skinBox
barColor: gConfig.reserverColor
backgroundWidth: 280
backgroundHeight: 180
contentItem: GridView {
anchors.fill: parent
anchors.margins: 10
model: gConfig.themes
cellWidth: 80
cellHeight: 80
delegate: Item {
width: 80
height: 80
Rectangle { //表示主題色的色塊
anchors.fill: parent
anchors.margins: 4
height: width
color: model.titleBackground
}
Rectangle { //主題色邊框,鼠標懸浮時顯示
anchors.fill: parent
color: "transparent"
border.color: model.titleBackground
border.width: 2
visible: a.containsMouse
}
Text { //主題名字
anchors {
left: parent.left
bottom: parent.bottom
leftMargin: 8
bottomMargin: 8
}
color: "white"
text: model.name
}
Rectangle { //右下角圓圈圈,當前選中的主題
x: parent.width - width
y: parent.height - height
width: 20
height: width
radius: width / 2
color: model.titleBackground
border.width: 3
border.color: gConfig.reserverColor
visible: gConfig.currentTheme === index
}
MouseArea { //鼠標狀態
id: a
anchors.fill: parent
hoverEnabled: true
onClicked: { //切主題操作
gConfig.currentTheme = index
}
}
}
}
}
}
帶三角形尖尖的彈窗組件
// TPopup.qml
import QtQuick 2.9
import QtQuick.Controls 2.5
Item {
id: root
anchors.fill: parent
property alias popupVisible: popup.visible
property alias contentItem: popup.contentItem
property color barColor: "white"
property alias backgroundItem: background
property real backgroundWidth: 200
property real backgroundHeight: 160
property color borderColor: barColor
property real borderWidth: 0
property real verticalOffset: 20
//矩形旋轉45度,一半被toolTip遮住(重合),另一半三角形和ToolTip組成一個帶箭頭的ToolTip
Rectangle {
id: bar
visible: popup.visible
rotation: 45
width: 16
height: 16
color: barColor
//水平居中
anchors.horizontalCenter: parent.horizontalCenter
//垂直方向上,由ToolTip的y值,決定位置
anchors.verticalCenter: parent.bottom
anchors.verticalCenterOffset: verticalOffset
}
Popup {
id: popup
width: backgroundWidth
height: backgroundHeight
background: Rectangle {
id: background
color: barColor
radius: 8
border.color:borderColor
border.width: borderWidth
}
}
function show() {
popup.x = (root.width - popup.width) / 2
popup.y = root.height + verticalOffset
popupVisible = true
}
function hide() {
popupVisible = false
}
}
轉載聲明
文章出自濤哥的博客
文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作 © 濤哥
聯繫方式
作者 | 濤哥 |
---|---|
開發理念 | 弘揚魯班文化,傳承工匠精神 |
博客 | https://jaredtao.github.io |
知乎 | https://www.zhihu.com/people/wentao-jia |
郵箱 | [email protected] |
微信 | xsd2410421 |
759378563 |
請放心聯繫我,樂於提供諮詢服務,也可洽談商務合作相關事宜。
打賞
如果覺得濤哥寫的還不錯,還請爲濤哥打個賞,您的讚賞是濤哥持續創作的源泉。