玩转Qml(3)-换皮肤
            
         
        
        
        
        
            本文于
                1059 
            天之前发表,文中内容可能已经过时。
        
        
     
    
        
简介 本文是《玩转Qml》系列文章的第三篇,涛哥将教大家,如何在Qml中实现动态换皮肤。顺带会分享一些Qt小技巧。
源码 《玩转Qml》系列文章,配套了一个优秀的开源项目:TaoQuick
github https://github.com/jaredtao/TaoQuick 
访问不了或者速度太慢,可以用国内的镜像网站gitee
https://gitee.com/jaredtao/TaoQuick 
效果预览 效果类似于网易云音乐
顺便说一下,这是涛哥创建的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,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Item  {    QtObject  {           id:  dataObj        property string name: "Hello"                                                 onNameChanged:  {            console.info("name:" , name)         }     }     ...     Button  {         ...         onClicked:  {                dataObj.name = "World" ;          }     }     ... } 
 
(哈哈,工作量和心理负担一下子减轻了很多,头发的数量也能保住了。再也不想回去写Qt/C++的属性了。)
全局单例 涛哥写了一个单独的qml文件,顶层就是一个QtObject类型,里面会有一大堆属性。
颜色、字体一类的配置都在这里。
1 2 3 4 5 6 7 8 9 import  QtQuick 2.0 QtObject  {    property  color titleBackground : "#c62f2f"         property  color background : "#f6f6f6"              property  color reserverColor : "#ffffff"           property  color textColor : "black"                  } 
 
然后在main.qml中实例化它
1 2 3 4 5 6 7 8 9 Item  {    width : 800      height : 600      GlobalConfig  {         id:  gConfig      }     ... } 
 
Qml有个特性,子页面实例可以通过id访问父页面中的实例,读 写其属性、调用其函数。
在main.qml中实例化的对象,相当于是全局的了,它的id是可以在所有main.qml的子页面中访问到的。
(当然还有一种方式,通过qmldir指定单例,这种留着后面再说)
实现 皮肤的配置和原理 下面是TaoQuick中使用的gConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 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时重新读属性,修改后的颜色自动就生效了。
这里顺带说一下,主题的颜色相关属性越少越好,因为太多了不容易识别、不好维护,
之前的文章《玩转Qml(1)-从按钮开始》中提到的Qt.lighter和Qt.darker,
就是一种减少颜色属性数量的神器。
皮肤选择器 再来看一下,涛哥参考 网易云音乐 做的皮肤选择器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 TImageBtn  {         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                     }                 }             }         }              } } 
 
带三角形尖尖的弹窗组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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           Rectangle  {         id:  bar          visible : popup.visible         rotation : 45          width : 16          height : 16          color : barColor                  anchors.horizontalCenter : parent .horizontalCenter                  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      } }