二、ListView的简单使用
ListView 用来显示一个条目列表,条目对应的数据来自于Model,而每个条目的外观则由 Delegate 决定。我们可以将 Delegate 看成如何展示 Item 的一个模板。Android 手机上常见 的联系人界面,其实就是使用 ListView 实现的,而且 Android 的 ListView 和 Qt Quick 的 ListView 使用同样的模式:Model、View、Item Template (Delegate)。
我们先以 Qt Quick 内建 Model 为例,把使用 ListView 的方方面面都介绍一下,然后再看如何使用在 C++ 中实现自定义的 Model。
我构建了一个简单的手机列表,展示手机的型号、价格、制造商。使用上下键可以切换不同的手机,选中的手机有一个浅蓝色的高亮背景,同时字体放大,文字颜色变为红色。代码 phone_list_simple.qml:
i
mport QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
width: 360
height: 300
color: "#EEEEEE"
// 1.定义delegate,内嵌三个Text对象来展示Model定义的ListElement的三个role
Component {
id: phoneDelegate
Item {
id: wrapper
width: parent.width
height: 30
// 实现了鼠标点选高亮的效果
MouseArea {
anchors.fill: parent;
onClicked: wrapper.ListView.view.currentIndex = index
}
// 内嵌三个Text对象,水平布局
RowLayout {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Text {
id: col1;
text: name;
// 是否是当前条目
color: wrapper.ListView.isCurrentItem ? "red" : "black"
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
Layout.preferredWidth: 120
}
Text {
text: cost;
color: wrapper.ListView.isCurrentItem ? "red" : "black"
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
Layout.preferredWidth: 80
}
Text {
text: manufacturer;
color: wrapper.ListView.isCurrentItem ? "red" : "black"
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
Layout.fillWidth: true
}
}
}
} // phoneDelegate-END
// 2.定义ListView
ListView {
id: listView
anchors.fill: parent
// 使用先前设置的delegate
delegate: phoneDelegate
// 3.ListModel专门定义列表数据的,它内部维护一个 ListElement 的列表。
model: ListModel {
id: phoneModel
// 一个 ListElement 对象就代表一条数据
ListElement{
name: "iPhone 3GS"
cost: "1000"
manufacturer: "Apple"
}
ListElement{
name: "iPhone 4"
cost: "1800"
manufacturer: "Apple"
}
ListElement{
name: "iPhone 4S"
cost: "2300"
manufacturer: "Apple"
}
ListElement{
name: "iPhone 5"
cost: "4900"
manufacturer: "Apple"
}
ListElement{
name: "B199"
cost: "1590"
manufacturer: "HuaWei"
}
ListElement{
name: "MI 2S"
cost: "1999"
manufacturer: "XiaoMi"
}
ListElement{
name: "GALAXY S5"
cost: "4699"
manufacturer: "Samsung"
}
}
// 背景高亮
focus: true
highlight: Rectangle{
color: "lightblue"
}
}
}
执行 “qmlscene phone_list_simple.qml” 命令,可以看到如下图所示的效果。
为了示例简单,我直接在声明 ListView 对象时为 model 属性初始化了一个 ListModel。ListModel 是专门定义列表数据的,它内部维护一个 ListElement 的列表。一个 ListElement 对象就代表一条数据。
使用 ListElement 定义的数据条目可能是简单的,比如只有一个人名;也可能是复杂的,比如还有这个人的出生年月、性别;共同构成一个 ListElement 的一个或多个数据信息被称为 role,它包含一个名字(role-name)和一个值(role-value)。
role 的定义就像 QML 对象属性定义那样简单,语法是这样的:: ,其中 role-name 必须以小写字母开头,role-value 必须是简单的常量,如字符串、布尔值、数字或枚举值。
在 ListElement 中定义的 role,可以在 Delegate 中通过 role-name 来访问。示例定义的 ListElement 包含三个 role:name、cost、manufacturer,而 Delegate 则使用 Row 管理三个 Text 对象来展现这三个 role, Text 对象的 text 属性被绑定到 role-name 上。
ListView 的 delegate 属性类型是 Component,我在 phone_list_simple.qml 中定义了 id 为 phoneDelegate 的 Component。phoneDelegate 的顶层是 RowLayout,RowLayout内嵌三个 Text 对象来展示 Model 定义的 ListElement 的三个 role。
ListView 给 delegate 暴露了一个 index 属性,代表当前 delegate 实例对应的 Item 的索引位置,必要时可以使用它来访问数据。
示例中实现了鼠标点选高亮的效果:给 delegate 添加了一个 MouseArea 元素,在 onClicked 信号处理器中设置 ListView 的 currentlndex 属性。
ListView 定义了 delayRemove、isCurrentltem、nextSection、previousSection、section、view 等附加属性,以及 add、remove 两个附加信号,可以在 delegate 中直接访问。不过要注意的是,只有 delegate 的顶层 Item 才可以直接使用这些附加属性和信号,非顶层 Item 则需通过顶层Item的id来访问这些附加属性。
示例中的 delegate 组件,顶层 Item 是一个 Item 对象, 用于展示 name、cost、manufacturer 的 Text 对象通过 wrapper.ListView.isCurrentltem判断本 delegate 实例呈现的数据是否是当前条目,如果是,则改变文字大小和颜色。注意,我们是通过类名直接访问附加属性的。
示例中当前选中条目有一个浅蓝色的背景,它由 ListView 的 highlight 属性指定的 Component 提供,它的 Z 序小于 delegate 实例化出来的 Item 对象。示例通过给 highlight 初始 化一个 Rectangle 定义了高亮背景,如果你想实现复杂的高亮效果,也可以专门定义一个 Component。
与高亮效果相关的,还有很多属性,比如 highlightFollowsCurrentltem 属性指定高亮背景是否跟随当前条目,默认值为 true,你用鼠标点选某个 Item 时,高亮背景会经过一个平滑的动画后移动到新的 Item 下面。你可以设置它为 false 来禁用这种动画。
回到顶部
三、header
通过为 ListView 的 header 属性设置一个 Component,,用方向键浏览 Item 或者用鼠标在 ListView 内拖动时,表头随着拖动可能会变得不可见。
表头在某些应用场景下可以让数据的可读性更好。比如前面的手机信息示例,如果添加了表头,别人一看就知道每一列的数据含义。phone_list_header.qml 是修改后的文件,内容如下:
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
width: 360
height: 300
color: "#EEEEEE"
// 1.定义header
Component {
id: headerView
Item {
width: parent.width
height: 30
RowLayout {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Text {
text: "Name"
font.bold: true
font.pixelSize: 20
Layout.preferredWidth: 120
}
// 省略。。。
}
}
}
// 2.定义delegate
Component {
id: phoneDelegate
Item {
id: wrapper
width: parent.width
height: 30
MouseArea {
anchors.fill: parent
onClicked: {
wrapper.ListView.view.currentIndex = index
console.log("index=", index)
}
}
RowLayout {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Text {
id: col1;
text: name;
color: wrapper.ListView.isCurrentItem ? "red" : "black"
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18
Layout.preferredWidth: 120
}
// 省略。。。
}
}
}
// 3.定义model
Component {
id: phoneModel
ListModel {
ListElement{
name: “iPhone 3GS”
cost: “1000”
manufacturer: “Apple”
}
// 省略。。。
}
}
// 4.定义ListView
ListView {
id: listView
anchors.fill: parent
delegate: phoneDelegate
model: phoneModel.createObject(listView)
header: headerView
focus: true
highlight: Rectangle{
color: "lightblue"
}
}
}
效果如下图所示。
headerView 是我定义的表头组件,与 delegate 组件定义类似,使用三个 Text 对象分别来描述每一列数据的含义,设定字体大小,让字体变粗,还设定了每一列的宽度。ListView 的 headerltem 属性保存了本 ListView 使用的、由 header 组件创建出来的 Item。
回到顶部
四、footer
footer 属性允许我们指定 ListView 的页脚,footerltem 保存了 footer 组件创建出来的 Item 对象,这个 Item会被添加到 ListView 的末尾,在所有可见的 Item 之后。
用 footer 可以干什么呢?随你吧。我这里的示例只是简单地在footer内放置了一个 Text对象,显示当前选中的Item的数据。有点儿像状态栏。
Rectangle {
width: 360
height: 300
color: "#EEEEEE"
// 省略header。。。
// 2. 定义footer
Component {
id: footerView
Text {
width: parent.width
font.italic: true
color: "blue"
height: 30
verticalAlignment: Text.AlignVCenter
}
}
// 省略delegate和model。。。
// 5.定义ListView
ListView {
id: listView
anchors.fill: parent
delegate: phoneDelegate
model: phoneModel.createObject(listView)
header: headerView
footer: footerView
focus: true;
highlight: Rectangle{
color: "lightblue"
}
onCurrentIndexChanged:{
if( listView.currentIndex >=0 ){
var data = listView.model.get(listView.currentIndex);
listView.footerItem.text = data.name + " , " + data.cost + " , " + data.manufacturer
}
}
}
}
效果如下图所示。
为了使 footer 能够跟随当前 Item 发生变化,我为 listView 定义了 onCurrentlndexChanged 信号处理器,因为 currentlndexChanged 信号不带参数,所以只能再次访问 currentlndex 属性来获取当前 Item 的索引,然后通过 ListModel 的 get() 方法获取到对应的数据对象,最后呢, 我把 name、cost、manufacturer 三个 role 拼接在一块赋值给 footerltem。于是乎,当你点选一 个 Item 或者使用上下键浏览 Item 时,footer 就变化了。