导语
上一篇内容中我们提到了QML中一些最基本的概念和语法,也许有的同学短时间内还无法完全理解透彻,那么,也没有必要再去死抠那些术语了,因为更多的概念和内容已经到来!
下面的内容比较多,我们无心让大家为了学习语法而学习,所以,咱们的主线是设计一个小程序。真正想理解一个概念必须把它放到实例中去理解,虽然,大家学习的很多时候也有示例,但是,那只是简短的几行代码,并不能完全展示应有的语境。
这次我们换一种全新的方式,在编写实例程序的同时来学习QML语法,下面咱们开始!
让界面美观起来
上一篇教程中写的程序界面过于简单,完全影响了我们学习的心情和进一步探索的好奇心,所以,咱们先来对界面进行一些美化。
1.美化字体和圆形
首先在 text1 对象定义中添加如下代码:
font.bold: true
font { pointSize: 14; capitalization: Font.AllUppercase }
Behavior on rotation {
NumberAnimation { duration: 500 }
}
这里使用 font 设置了 text1 的文本字体。然后下面为 text1 的 rotation 旋转属性设置了动画效果,这个咱们后面学习动画的时候再详细介绍。
然后将 colorRect 对象的定义修改如下:
Rectangle {
id: colorRect
width: 20 * 2
height: width
radius: 20
border.color: "green"
anchors.left: text1.right
anchors.leftMargin: 10
anchors.verticalCenter: text1.verticalCenter
MouseArea {
anchors.fill: parent
onClicked: {
console.debug("colorRect: ", parent.border.color)
text1.rotation += 360
text1.color = parent.border.color
}
}
Rectangle {
width: 12 * 2
height: width
radius: 12
color: parent.border.color
anchors.centerIn: parent
}
}
这里主要是将以前绿色圆形修改为了绿色圆环,然后在其中间添加了一个小的绿色圆形对象;在MouseArea 点击处理中让 text1 旋转360度,并且设置 text1 的文本颜色为圆形的颜色。现在运行的效果如下图所示。
2.添加装饰图片
为了让界面更加美观,我们添加几张图片来做装饰。首先将需要的图片放到源码目录,一般是新建一个目录单独存放,比如这里放到了 images 目录中。然后在Qt Creator左上角项目文件列表的 qml.qrc 上右击,选择 Open in Editor
菜单项,如下图所示。
然后在资源文件编辑器界面下方选择添加文件,选中images目录中所有图片,完成后在资源文件编辑器界面按下Ctrl + S 快捷键保存修改。这样就可以在左侧项目文件列表中看到 images 目录以及其中添加的图片文件了。如下图所示。
下面在 Window 根对象最后添加一个 Image类型的图片对象定义:
Image {
id: backImg
source: "images/bg1.png"
width: parent.width
anchors.bottom: parent.bottom
fillMode: Image.PreserveAspectFit
}
Image类型在后面也会详细讲解,一般只需要指定它的大小、路径source属性、填充模式fillMode属性即可。使用资源系统中的文件,需要指明路径,比如这里的 "images/bg1.png"
;填充模式 Image.PreserveAspectFit
表明在缩放图片时会保持宽高比不变,这样图片不会被拉伸变形。下面是运行效果。
接下来我们在colorRect 对象的MouseArea 子对象的 onClicked: { }
中添加一行代码实现点击colorRect 切换装饰图片的效果:
backImg.source = "images/bg1.png"
3.实现圆形的交互特效
现在点击圆形可以让文本变换颜色并旋转,并且底部的图片也会更换。为了点击这个圆形时能给用户一个好的反馈,我们为其添加鼠标指针进入和移出时不同效果的设置,其实也挺简单的,只是在 MouseArea 的最后面添加如下代码:
hoverEnabled: true
onEntered: {
parent.width = 32
parent.color = "black"
}
onExited: {
parent.width = 40
parent.color = "white"
}
因为MouseArea默认是鼠标点击后才会有效果,为了让鼠标指针进入MouseArea区域就可以进行相应设置,需要使用 hoverEnabled: true
开启悬停。然后分别设置了鼠标进入、移出时圆形的不同效果。
4.复制圆形按钮
现在我们的绿色圆形已经具有了简单的按钮功能,下面我们再复制两个这样的按钮到text1文本的不同位置,其实就是复制colorRect的定义代码,然后简单更改 id、border.color、anchors、backImg.source
等处代码,最终运行效果如下图所示。因为复制后代码较多,就不再列出。
到这里程序要实现的功能已经完成了,不过,代码写成这样会妨碍我们走得更远,所以,教程到这里只是开始,下面我们继续!
自定义组件
前面的代码写完已经150多行了,在main文件中写这么多代码并不是什么好习惯。下面我们将 Text 和所有 Rectangle 对象的代码都剪切到单独的文件中。
1.自定义对象类型和组件
在项目中添加新的qml文件,因为现在qml文件默认都在资源文件中,所以我们需要向qml.qrc文件中添加新文件。在项目文件列表 qml.qrc 文件下面的“/”目录上右击,在弹出菜单中选择 “Add New” ,如下图所示。
在弹出的新建文件对话框中,选择Qt分类下的 QML File (Qt Quick 2)模板,如下图所示。
文件名称设置为 ColorText
,注意首字母大写。完成后ColorText.qml文件内容如下:
// ColorText.qml
import QtQuick 2.9
Item {
id: root
}
自动生成的 import 语句导入的可能是 QtQuick 2.0,这里修改为了 2.9 。Item 类型是Qt Quick中所有可视类型的基类型,比如前面的 Text、Rectangle 等类型都继承自 Item 。因为这里Item作为根对象,所以我们为其设置了 id为 root 。
接下来,我们把 main.qml 中的 Text 和所有 Rectangle 类型对象的代码都剪切到 Item 中。
完成后需要将代码中 backImg.source = "images/bg1.png"
这样的三行代码删除掉。
然后到main.qml文件中,在 Window 对象的最后添加如下代码:
ColorText {
anchors.centerIn: parent
}
可以看到我们能够直接使用 ColorText.qml 文件的名称作为类型来创建对象。
-
使用QML文件定义对象类型
像这种将一个QML文档放到一个以 TypeName.qml
命名的文件中,就定义了一个对象类型,类型名称就是 TypeName
,名称必须以大写字母开头,不能包含除字母、数字和下划线以外的字符。这个文档会被QML引擎自动识别为一个QML类型的定义,在同一个目录中的其他QML文件会被自动设置为可用的。所以,这里我们可以像使用一个普通QML对象类型一样来使用 ColorText 。
-
组件(Component)
组件是可重用的、封装好的、并提供了已定义接口的QML类型,最常见的组件就是一个单独的QML文件,比如这里的 ColorText.qml 文件就定义了一个组件。除此之外,还可以使用Component类型在文档中定义组件,这里就不再开展讲述了。
为什么要将代码放到一个单独的文件中,除了前面说的可以简化 main.qml 文件以外,还有可封装、可重用等好处。到这里我们的组件已经可用了,但是在外面使用封装好的组件,要实现一定的功能,还必须提供接口。
2.自定义信号
我们在 ColorText.qml 的 Item 根对象前面添加一个信号定义:
Item {
id: root
signal clicked(string buttonColor)
... ...
}
然后在下面三个Rectangle对象的MouseArea子对象的onClicked中分别调用该信号:
root.clicked(parent.border.color)
现在回到 main.qml 文件中,将ColorText 对象定义更改如下:
ColorText {
anchors.centerIn: parent
onClicked: {
console.log("colorButton: ", buttonColor)
var value = buttonColor
switch(value) {
case "#ff0000": backImg.source = "images/bg2.png"; break;
case "#0000ff": backImg.source = "images/bg3.png"; break;
default: backImg.source = "images/bg1.png";
}
}
}
当信号发射后,可以在 onClicked 信号处理器中执行需要的操作,这里根据信号发射参数的不同,显示不同的图片。现在运行程序,已经恢复了我们在第一节完成的代码功能。
-
信号特性(Signal Attributes)和信号处理器特性(Signal Handler Attributes)
如果大家以前有Qt的基础,那么对信号和槽应该不陌生。在QML中,信号是发生事件的对象发出的通知,比如前面我们用到的 MouseArea 对象被点击了,会发射 clicked 信号,这时可以在对应的信号处理器( Qt C++ 中叫做槽)中获得通知,信号处理器的格式是 on<Signal> ,其中 <Signal> 是信号的名称(首字母必须大写),所以前面我们一直在 onClicked 中进行点击按钮后的操作。
在QML文档中定义一个信号可以使用如下语法:
signal <signalName>[([<type> <parameter name>[, ...]])]
例如这里定义的 clicked 信号:
signal clicked(string buttonColor)
如果信号有参数,那么参数类型必须声明,如果没有参数,那么可以省略后面的小括号。
发射一个信号,就是调用该信号。当一个信号被发射后,其对应的信号处理器就会被调用,在其中可以直接访问信号的参数。
另外,还可以使用connect()函数将信号与一个方法或者另外的信号进行关联,语法是:对象id.信号.connect (方法或者信号)
。
-
属性改变信号
QML类型提供了内建的属性改变信号,每当属性值改变时都会发射该信号,可以在对应的 on<PropertyName>Changed
信号处理器中进行相应的操作。
3.自定义属性
一个组件中只有根对象的属性可以在外部被调用,也就是说,ColorText.qml文件中创建的 Text 对象和其他 Rectangle对象中的属性是无法在main.qml中定义的 ColorText 对象中调用的。要想在外部访问组件内子对象的属性,需要通过在根对象中定义属性来实现。
我们在ColorText.qml文件的 Item 根对象中添加如下属性定义:
Item {
id: root
property string colorText // 定义一个string类型的colorText属性
signal clicked(string buttonColor)
... ...
然后修改 Text 对象中 text 属性:
Text {
id: text1
text: colorText
... ...
这样,就可以到main.qml文件,在 ColorText 对象中修改文本的内容了:
ColorText {
colorText: qsTr("hello world!")
... ...
当然,这里只是将自定义的属性当做了一个桥梁,如果只是这个目的,那么也可以不使用自定义属性,而是使用属性别名。属性别名更适合将组件内子对象属性暴露给外部对象。我们在Item根对象中继续添加这样一行代码:
property alias textFont: text1.font
然后就可以在 main.qml 的 ColorText 对象处这样来使用:
textFont.underline: true
可以看到,对于导出组件中的属性,使用属性别名更方便。
-
定义属性特性(Property Attributes)
可以使用如下语法来自定义一个属性:
[default] property <propertyType> <propertyName> : <value>
第一个default修饰符是可选的,如果有则表明定义了一个默认属性; propertyType是属性类型,QML的基本类型、对象类型都可以作为属性的类型;属性名称 propertyName 的命名规则与 id 类似,必须以小写字母开头,可以包含字母、数字和下划线。在定义属性的同时可以为其初始化,如果不需要初始化,那么后面的 : <value>
可以省略。
-
默认属性
一个对象至多有一个默认属性,当为该对象添加子对象时,如果没有明确指定子对象要分配到的属性名,那么子对象就会被赋值给默认属性。前面我们看到子对象都是直接写的,一般不会赋值给一个属性,这是因为,所有基于Item 的类型都有一个默认属性 data 。
-
属性别名(Property Aliases)
与普通的定义属性不同,属性别名不需要分配一个新的唯一的存储空间,而只是将新声明的属性(称为别名属性)作为已存在属性(称为被别名的属性)的直接引用。相比较语法而言,定义属性别名需要使用alias关键字代替属性类型,并且右侧的赋值(被别名的属性)不可省略:
[default] property alias <name>: <alias reference>
4.自定义方法
对于复杂的操作我们可以在函数中进行,QML中的方法特性可以让我们自定义方法。我们在 Item 根对象中添加如下代码:
function changeDuration(duration) {
animation.duration = duration
}
这里定义了一个函数来更改动画的持续时间。为了可以访问text1对象中的动画对象,需要为其设置id:
NumberAnimation { id: animation; duration: 500 }
现在就可以在main.qml的 ColorText 对象中调用该函数来修改动画持续时间了:
onClicked: {
changeDuration(2000)
... ...
现在可以运行程序,测试下效果。大家可能会发现,第一次点击按钮后,动画的持续时间并没有改变,等第二次点击按钮时才会生效。这是因为,当ColorText对象被实例化的时候,duration: 500
已经生效了,当按钮点击信号发**的时候,动画已经开始执行了,所以在这里设置的值没有对动画立即生效。
怎么能在 ColorText 对象创建完成的时候就执行changeDuration()函数呢,可以通过 Component.onCompleted
来完成。在main.qml 的 ColorText 对象定义中添加如下代码:
Component.onCompleted: {
changeDuration(2000)
}
这样当对象被创建以后就会立即执行,所以我们编译运行程序后,动画持续时间已经改变了。
为了不留遗憾,虽然我们要讲的知识已经讲完了,但是我们还是再修改下 ColorText 对象中的相关代码:
onClicked: {
console.log("colorButton: ", buttonColor)
var value = buttonColor
switch(value) {
case "#ff0000":
backImg.source = "images/bg2.png";
changeDuration(500);
break;
case "#0000ff":
backImg.source = "images/bg3.png";
changeDuration(1000);
break;
default:
backImg.source = "images/bg1.png";
changeDuration(2000);
}
}
这样,当点击不同的按钮,会更改不同的图片并为文本设置新的旋转速度。最后,附上该程序在手机端运行效果的截图。
-
方法特性(Method Attributes)
一个对象类型的方法就是一个函数,可以用来执行一些处理或者触发其他事件。QML的方法可以用来定义相对独立的可重用的 JavaScript 代码块,这些方法可以在内部调用,也可以被外部对象调用。定义方法的语法如下:
function <functionName>([<parameterName>[, ...]]) { <body> }
functionName是函数名称,一般首字母小写;参数是可选的,如果有,不需要指明参数类型,默认是var类型,可以在函数体中通过参数名称来访问这些参数。
-
在启动时执行操作(Component.onCompleted)
每一个QML对象类型都包含一个附加的 Component 属性,在对象被实例化完成后该属性会发射completed信号,其对应的onCompleted 处理器会在QML环境完全建立以后执行。所以,我们想要在程序启动后执行一些操作,可以放到 Component.onCompleted 中执行。
结语
这篇教程内容比较多,本来可以分成两篇文章来写,但是整篇的内容是一个有机整体,为了不打断学习的思路,我们放到了一篇教程中呈现给大家。也希望真正想学习的读者可以从头自己敲一遍代码,不要嫌费事,这篇文章的每一个字包括代码都是我亲自手敲的,本来有现成的东西可以复制粘贴,但是为了理顺思路,获得灵感,还是要亲力亲为的。作为初学者,如果连代码都懒得敲,那么也就没有必要学下去了。