Qml組件化編程3-動態切換皮膚

簡介

本文是《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時重新讀屬性,修改後的顏色自動就生效了。

這裏順帶說一下,主題的顏色相關屬性越少越好,因爲太多了不容易識別、不好維護,

文章1 Qml組件化編程1-按鈕的定製與封裝

中提到的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
QQ 759378563

請放心聯繫我,樂於提供諮詢服務,也可洽談商務合作相關事宜。

打賞


如果覺得濤哥寫的還不錯,還請爲濤哥打個賞,您的讚賞是濤哥持續創作的源泉。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章