#(85):动态视图
`Repeater`适用于少量的静态数据集。但是在实际应用中,数据模型往往是非常复杂的,并且数量巨大。这种情况下,`Repeater`并不十分适合。于是,QtQuick 提供了两个专门的视图元素:`ListView`和`GridView`。这两个元素都继承自`Flickable`,因此允许用户在一个很大的数据集中进行移动。同时,`ListView`和`GridView`能够复用创建的代理,这意味着,`ListView`和`GridView`不需要为每一个数据创建一个单独的代理。这种技术减少了大量代理的创建造成的内存问题。
由于`ListView`和`GridView`在使用上非常相似,因此我们以`ListView`为例进行介绍。
`ListView`类似[前面章节](http://www.devbean.net/2014/06/qt-study-road-2-qml-repeater/)提到的`Repeater`元素。`ListView`使用模型提供数据,创建代理渲染数据。下面是`ListView`的简单使用:
~~~
import QtQuick 2.2
Rectangle {
width: 80
height: 300
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: numberDelegate
spacing: 5
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
代码运行结果如下图所示:
[![](https://box.kancloud.cn/2015-12-29_56823287ed958.png)](http://files.devbean.net/images/2014/07/listview-demo.png)
如果数据模型包含的数据不能在一屏显示完全,`ListView`只会显示整个列表的一部分。但是,作为 QtQuick 的一种默认行为,`ListView`并不能限制显示范围就在代理显示的区域内。这意味着,代理可能会在`ListView`的外部显示出来。为避免这一点,我们需要设置`clip`属性为`true`,使得超出`ListView`边界的代理能够被裁减掉。注意下图所示的行为(左面是设置了`clip`的`ListView`而右图则没有):
[![](https://box.kancloud.cn/2015-12-29_5682328809f39.png)](http://files.devbean.net/images/2014/07/listview-clip.png)
对于用户而言,`ListView`是一个可滚动的区域。`ListView`支持平滑滚动,这意味着它能够快速流畅地进行滚动。默认情况下,这种滚动具有在向下到达底部时会有一个反弹的特效。这一行为由`boundsBehavior`属性控制。`boundsBehavior`属性有三个可选值:`Flickable.StopAtBounds`完全消除反弹效果;`Flickable.DragOverBounds`在自由滑动时没有反弹效果,但是允许用户拖动越界;`Flickable.DragAndOvershootBounds`则是默认值,意味着不仅用户可以拖动越界,还可以通过自由滑动越界。
当列表滑动结束时,列表可能停在任意位置:一个代理可能只显示一部分,另外部分被裁减掉。这一行为是由`snapMode`属性控制的。`snapMode`属性的默认值是`ListView.NoSnap`,也就是可以停在任意位置;`ListView.SnapToItem`会在某一代理的顶部停止滑动;`ListView.SnapOneItem`则规定每次滑动时不得超过一个代理,也就是每次只滑动一个代理,这种行为在分页滚动时尤其有效。
默认情况下,列表视图是纵向的。通过`orientation`属性可以将其改为横向。属性可接受值为`ListView.Vertical`或`ListView.Horizontal`。例如下面的代码:
~~~
import QtQuick 2.2
Rectangle {
width: 480
height: 80
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
orientation: ListView.Horizontal
delegate: numberDelegate
spacing: 5
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
当列表视图横向排列时,其中的元素按照从左向右的顺序布局。使用`layoutDirection`属性可以修改这一设置。该属性的可选值为`Qt.LeftToRight`或`Qt.RightToLeft`。
在触摸屏环境下使用`ListView`,默认的设置已经足够。但是,如果在带有键盘的环境下,使用方向键一般应该突出显示当前项。这一特性在 QML 中称为“高亮”。与普通的代理类似,视图也支持使用一个专门用于高亮的代理。这可以认为是一个额外的代理,只会被实例化一次,并且只会移动到当前项目的位置。
下面的例子设置了两个属性。第一,`focus`属性应当被设置为`true`,这允许`ListView`接收键盘焦点。第二,`highlight`属性被设置为一个被使用的高亮代理。这个高亮代理可以使用当前项目的`x`、`y`和`height`属性;另外,如果没有指定`width`属性,也可以使用当前项目的`width`属性。在这个例子中,宽度是由`ListView.view.width`附加属性提供的。我们会在后面的内容详细介绍这个附加属性。
~~~
import QtQuick 2.2
Rectangle {
width: 240
height: 300
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: numberDelegate
spacing: 5
highlight: highlightComponent
focus: true
}
Component {
id: highlightComponent
Rectangle {
width: ListView.view.width
color: "lightGreen"
}
}
Component {
id: numberDelegate
Item {
width: 40
height: 40
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
运行结果如下图所示:
[![](https://box.kancloud.cn/2015-12-29_5682328818134.png)](http://files.devbean.net/images/2014/10/highlight-delegate.png)
在使用高亮时,QML 提供了很多属性,用于控制高亮的行为。例如,`highlightRangeMode`设置高亮如何在视图进行显示。默认值`ListView.NoHighlightRange`意味着高亮区域和项目的可视范围没有关联;`ListView.StrictlyEnforceRange`则使高亮始终可见,如果用户试图将高亮区域从视图的可视区域移开,当前项目也会随之改变,以便保证高亮区域始终可见;介于二者之间的是`ListView.ApplyRange`,它会保持高亮区域可视,但是并不强制,也就是说,如果必要的话,高亮区域也会被移出视图的可视区。
默认情况下,高亮的移动是由视图负责的。这个移动速度和大小的改变都是可控的,相关属性有`highlightMoveSpeed`,`highlightMoveDuration`,`highlightResizeSpeed`以及`highlightResizeDuration`。其中,速度默认为每秒 400 像素;持续时间被设置为 -1,意味着持续时间由速度和距离控制。同时设置速度和持续时间则由系统选择二者中较快的那个值。有关高亮更详细的设置则可以通过将`highlightFollowCurrentItem`属性设置为`false`达到。这表示视图将不再负责高亮的移动,完全交给开发者处理。下面的例子中,高亮代理的`y`属性被绑定到`ListView.view.currentItem.y`附加属性。这保证了高亮能够跟随当前项目。但是,我们不希望视图移动高亮,而是由自己完全控制,因此在`y`属性上面应用了一个`Behavior`。下面的代码将这个移动的过程分成三步:淡出、移动、淡入。注意,`SequentialAnimation`和`PropertyAnimation`可以结合`NumberAnimation`实现更复杂的移动。有关动画部分,将在后面的章节详细介绍,这里只是先演示这一效果。
~~~
Component {
id: highlightComponent
Item {
width: ListView.view.width
height: ListView.view.currentItem.height
y: ListView.view.currentItem.y
Behavior on y {
SequentialAnimation {
PropertyAnimation { target: highlightRectangle; property: "opacity"; to: 0; duration: 200 }
NumberAnimation { duration: 1 }
PropertyAnimation { target: highlightRectangle; property: "opacity"; to: 1; duration: 200 }
}
}
Rectangle {
id: highlightRectangle
anchors.fill: parent
color: "lightGreen"
}
}
}
~~~
最后需要介绍的是`ListView`的 header 和 footer。header 和 footer 可以认为是两个特殊的代理。虽然取名为 header 和 footer,但是这两个部分实际会添加在第一个元素之前和最后一个元素之后。也就是说,对于一个从左到右的横向列表,header 会出现在最左侧而不是上方。下面的例子演示了 header 和 footer 的位置。header 和 footer 通常用于显示额外的元素,例如在最底部显示“加载更多”的按钮。
~~~
import QtQuick 2.2
Rectangle {
width: 80
height: 300
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 4
delegate: numberDelegate
spacing: 5
header: headerComponent
footer: footerComponent
}
Component {
id: headerComponent
Rectangle {
width: 40
height: 20
color: "yellow"
}
}
Component {
id: footerComponent
Rectangle {
width: 40
height: 20
color: "red"
}
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
[![](https://box.kancloud.cn/2015-12-29_5682328825311.png)](http://files.devbean.net/images/2014/10/listview-header-footer.png)
需要注意的是,header 和 footer 与`ListView`之间没有预留间距。这意味着,header 和 footer 将紧贴着列表的第一个和最后一个元素。如果需要在二者之间留有一定的间距,则这个间距应该成为 header 和 footer 的一部分。
`GridView`与`ListView`非常相似,唯一的区别在于,`ListView`用于显示一维列表,`GridView`则用于显示二维表格。相比列表,表格的元素并不依赖于代理的大小和代理之间的间隔,而是由`cellWidth`和`cellHeight`属性控制一个单元格。每一个代理都会被放置在这个单元格的左上角。
~~~
import QtQuick 2.2
Rectangle {
width: 240
height: 300
color: "white"
GridView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
cellWidth: 45
cellHeight: 45
delegate: numberDelegate
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
[![](https://box.kancloud.cn/2015-12-29_5682328930b8a.png)](http://files.devbean.net/images/2014/10/gridview-demo.png)
与`ListView`类似,`GridView`也可以设置 header 和 footer,也能够使用高亮代理和类似列表的边界行为。`GridView`支持不同的显示方向,这需要使用`flow`属性控制,可选值为`GridView.LeftToRight`和`GridView.TopToBottom`。前者按照先从左向右、再从上到下的顺序填充,滚动条出现在竖直方向;后者按照先从上到下、在从左到右的顺序填充,滚动条出现在水平方向。
- (1)序
- (2)Qt 简介
- (3)Hello, world!
- (4)信号槽
- (5)自定义信号槽
- (6)Qt 模块简介
- (7)MainWindow 简介
- (8)添加动作
- (9)资源文件
- (10)对象模型
- (11)布局管理器
- (12)菜单栏、工具栏和状态栏
- (13)对话框简介
- (14)对话框数据传递
- (15)标准对话框 QMessageBox
- (16)深入 Qt5 信号槽新语法
- (17)文件对话框
- (18)事件
- (19)事件的接受与忽略
- (21)事件过滤器
- (22)事件总结
- (23)自定义事件
- (24)Qt 绘制系统简介
- (25)画刷和画笔
- (26)反走样
- (27)渐变
- (28)坐标系统
- (29)绘制设备
- (30)Graphics View Framework
- (31)贪吃蛇游戏(1)
- (32)贪吃蛇游戏(2)
- (33)贪吃蛇游戏(3)
- (34)贪吃蛇游戏(4)
- (35)文件
- (36)二进制文件读写
- (37)文本文件读写
- (38)存储容器
- (39)遍历容器
- (40)隐式数据共享
- (41)model/view 架构
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)视图和委托
- (47)视图选择
- (48)QSortFilterProxyModel
- (49)自定义只读模型
- (50)自定义可编辑模型
- (51)布尔表达式树模型
- (52)使用拖放
- (53)自定义拖放数据
- (54)剪贴板
- (55)数据库操作
- (56)使用模型操作数据库
- (57)可视化显示数据库数据
- (58)编辑数据库外键
- (59)使用流处理 XML
- (60)使用 DOM 处理 XML
- (61)使用 SAX 处理 XML
- (62)保存 XML
- (63)使用 QJson 处理 JSON
- (64)使用 QJsonDocument 处理 JSON
- (65)访问网络(1)
- (66)访问网络(2)
- (67)访问网络(3)
- (68)访问网络(4)
- (69)进程
- (70)进程间通信
- (71)线程简介
- (72)线程和事件循环
- (73)Qt 线程相关类
- (74)线程和 QObject
- (75)线程总结
- (76)QML 和 QtQuick 2
- (77)QML 语法
- (78)QML 基本元素
- (79)QML 组件
- (80)定位器
- (81)元素布局
- (82)输入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)动态视图
- (86)视图代理
- (87)模型-视图高级技术
- (88)Canvas
- (89)Canvas(续)