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
    邮箱 jared2020@163.com
    微信 xsd2410421
    QQ 759378563

    请放心联系我,乐于提供咨询服务,也可洽谈商务合作相关事宜。

    打赏

    weixin
    zhifubao


    如果觉得涛哥写的还不错,还请为涛哥打个赏,您的赞赏是涛哥持续创作的源泉。



Log in to reply
 

走马观花

最近的回复

  • 这个错误没多大影响,qt模块可以在QT setting里进行勾选

    read more
  • 如果对操作不是很熟悉或者是操作不便的话,也可以在Windows的命令行执行NDK的安装。参数是一样的:
    Windows命令行安装NDK

    read more
  • 忘记补充图片了。这里需要点击更新安装才行。😆
    点击更新安装

    read more
  • 问:
    如何在Qt Creator安装NDK呢?
    除了那个🔽 按钮外,还有别的办法吗?
    答:
    Qt Creaator 4.11中更加整合了Android SDK Manager。其实大家都可以不运行默认的Android SDK Manager,直接在Qt Creator中选项->设备->Android,就可以进行操作了。

    通过````高级选项……,启动一些Android SDK Manager,只需要带上参数ndk-bundle```。原来没有NDK的你,也可以通过Android SDK Manager安装了。很简单吧。
    Android SDK Manager安装NDK

    read more

关注我们

微博
QQ群