# 动态视图
重复器适用于有限的静态数据集,但在现实世界中,模型通常更复杂——数据量也更大。 在这里,需要一个更智能的解决方案。 为此,Qt Quick提供了ListView
和GridView
元素,它们都基于Flickable
(可滑动)区域,因此用户可以在更大的数据集中移动。 同时,它们限制了并发实例化代理的数量。 对于大型模型,这意味着一次场景中的元素更少。
这两种元素的用法相似。 首先分析ListView
,然后以与其对比的方式描述GridView
。 请注意,GridView
将项目列表放置到二维网格中,可以从左到右或从上到下。 如果要显示数据表,则需要使用“表模型”章节中描述的TableView
”。
ListView
与Repeater
类似,它使用model
实例化delegate
(代理),并且在代理之间可以有spacing
(空隙)。 下面显示了怎样简单设置外观:
import QtQuick
import "../common"
Background {
width: 80
height: 300
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: GreenBox {
required property int index
width: 40
height: 40
text: index
}
spacing: 5
}
}
如果模型包含的数据超出屏幕所能容纳的范围,则ListView
仅显示列表的一部分。 但是,由于Qt Quick的默认行为,列表视图不会限制显示代理的屏幕区域。 这意味着,代理可能在列表视图之外的区域可见,并且在列表视图之外动态创建和销毁代理对用户是可见的。 为了防止这种情况,必须通过在ListView
元素上将clip
属性设置为true
来激活剪切。 下图显示了与将clip
属性为false
时(右视图)与true
时(左视图)的对比。
对于用户来说,ListView
是一个可滚动的区域。它支持动态滚动,这意味着可以滑动它以快速移动内容。默认情况下,它可以被拖拽到显示超出本身内容末尾(最后的列表元素)的内容(拉伸出的部分空白),然后回弹,向用户发出已到达结尾的信号。
视图末尾的行为使用boundsBehavior
属性控制。这是一个枚举值,默认行为是Flickable.DragAndOvershootBounds
,视图可以拖动到超出边界,并有回弹效果;Flickable.StopAtBounds
,视图永远不会超出其边界;Flickable.DragOverBounds
, 允许用户将视图拖动到超出边界,但回弹在其边界处停止。
可以限制允许视图停止的位置,这使用snapMode
属性控制。ListView.NoSnap
,默认行为,可以让视图停在任何位置;ListView.SnapToItem
,内部项目的顶部将始终与视图的顶部对齐。ListView.SnapOneItem
,当释放鼠标按钮或触摸时,视图将最多移动不超过一个项目。在翻页时,最后一种模式非常方便。
# 方向
列表视图默认是垂直滚动,但同样可以水平滚动。 列表视图的方向通过orientation
属性控制,它可以设置为ListView.Vertical
(默认值)、ListView.Horizontal
。 水平列表视图如下所示:
import QtQuick
import "../common"
Background {
width: 480
height: 80
ListView {
anchors.fill: parent
anchors.margins: 20
spacing: 4
clip: true
model: 100
orientation: ListView.Horizontal
delegate: GreenBox {
required property int index
width: 40
height: 40
text: index
}
}
}
如痛所见的,默认情况下,在水平方向元素从左向右排列。 这可以通过layoutDirection
属性控制,根据不同需要的流向,该属性可以设置为Qt.LeftToRight
、Qt.RightToLeft
。
# 按键导航与高亮
在基于触摸的设置中使用ListView
,视图本身就能表达足够的含义。在一个有键盘,甚至用方向键选择一个项时,需要一种机制来指示当前的项。在QML中,将这种机制称作高亮显示。
视图支持高亮代理,它与那些代理一起显示在视图中。它可以被认为是一个额外的代理,只是它只被实例化一次,并且移动到与当前项相同的位置。
将在下面示例中演示,这涉及到两个属性。首先,将focus
属性设置为true,这给了ListView
键盘焦点;其次,highlight
属性设置成要使用的高亮代理。高亮代理被赋予当前项的x
、y
和height
;如果未指定width
,则还使用当前项的宽度。
在示例中,附加属性ListView.view.width
用于宽度。可用于代理的附加属性将在本章的有关代理部分的章节进一步讨论,但了解相同的属性也可用于高亮显示代理也很好。
import QtQuick
import "../common"
Background {
width: 240
height: 300
ListView {
id: view
anchors.fill: parent
anchors.margins: 20
focus: true
model: 100
delegate: numberDelegate
highlight: highlightComponent
spacing: 5
clip: true
}
Component {
id: highlightComponent
GreenBox {
width: ListView.view ? ListView.view.width : 0
}
}
Component {
id: numberDelegate
Item {
id: wrapper
required property int index
width: ListView.view ? ListView.view.width : 0
height: 40
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: wrapper.index
}
}
}
}
将ListView
与高亮显示结合使用时,可以使用许多属性来控制其行为。 highlightRangeMode
控制视图中显示的内容如何影响高亮显示。ListView.NoHighlightRange
,是默认设置,表示视图中可见范围与项目的高亮显示一点也不相关。 译者笔记:在这种模式下,如果是最下面超出可见范围,使用上下方向的方向按钮控制当前项的选择,新的当前项会显示在最下方;如果是从最上面超出显示范围,新选中的项将会出现在最上方;如果在可视范围内切换当前项,总体列表项的位置不上下移动,只有当前选中项的效果上下移动。
ListView.StrictlyEnforceRange
确保高亮显示的项始终可见。如果一个动作试图将高亮部分移动出视图的可见部分,当前项也将相应改变,以便高亮项始终保持可见。译者笔记:在这种模式下,当前项会呈现在可视范围的最上面,即使是最后一项,也会呈现在可视区域中的第一项,其下面的区域将会空白。
上面两个的折中的值是ListView.ApplyRange
,它尝试保持高亮的项可见,但不会更改当前项目来强制实现其可见性; 相反,如有必要,允许将高亮显示项移出视图。译者笔记:在这种模式下,当前项会呈现在可视范围的最上面;但是在列表中最后一项出现在可见区域的下方时,高亮项将会能向下移动,最后课到达可视范围的最下方。
在默认配置中,视图负责将高亮区域移动到对应位置。移动和调整尺寸的速度可以被速度或持续时间控制,涉及的属性是highlightMoveSpeed
、highlightMoveDuration
、highlightResizeSpeed
和highlightResizeDuration
。默认情况下,速度设置为每秒400个像素,持续时间设置为-1,表示使用速度和距离控制持续时间。如果同时设置了速度和持续时间,则会选择生成最快动画的那个。
想要更细地控制高亮区域的移动,可以将highlightFollowCurrentItem
属性设置为false
。这意味着视图不再负责高亮代理的移动;相反,可以通过Behavior
或动画来控制移动。
在下面的示例中,高亮代理的y
属性绑定到附加属性ListView.view.currentItem.y
上,这确保高亮显示跟随当前项。然而,由于不让视图移动区域,可以控制元素的移动方式,这通过Behavior on y
完成。在下面的示例中,运动分为三个步骤:淡出(fading out)、移动(moving)、淡入(fading in)。请注意如何将SequentialAnimation
和PropertyAnimation
元素与NumberAnimation
结合使用以创建更复杂的运动。
Component {
id: highlightComponent
Item {
width: ListView.view ? ListView.view.width : 0
height: ListView.view ? ListView.view.currentItem.height : 0
y: ListView.view ? ListView.view.currentItem.y : 0
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 }
}
}
GreenBox {
id: highlightRectangle
anchors.fill: parent
}
}
}
# 页眉和页脚
在ListView
内容的两端,可以插入一个header
和一个footer
元素,它们可以被视为位于列表开头或结尾的特殊代理。 对于水平列表,它们不会出现在头部或脚部,而是出现在开头或结尾,具体出现位置取决于所使用的layoutDirection
。
下面的示例说明了如何使用页眉和页脚来增强列表开头和结尾的观感。 这些特殊列表元素还有其他用途,例如,它们可用于放置按钮用来加载更多的内容。
import QtQuick
import "../common"
Background {
width: 240
height: 300
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 4
delegate: numberDelegate
header: headerComponent
footer: footerComponent
spacing: 2
}
Component {
id: headerComponent
YellowBox {
width: ListView.view ? ListView.view.width : 0
height: 20
text: 'Header'
}
}
Component {
id: footerComponent
YellowBox {
width: ListView.view ? ListView.view.width : 0
height: 20
text: 'Footer'
}
}
Component {
id: numberDelegate
GreenBox {
required property int index
width: ListView.view.width
height: 40
text: 'Item #' + index
}
}
}
提示
页眉和页脚代理不遵守ListView
的spacing
(间距)属性,而是将它们直接放置在列表中的挨着的代理的旁边。 这意味着它的任何间距都必须是页眉和页脚项的一部分。
# 网格视图(GridView)
GridView
(网格视图)的使用与ListView
(列表视图)非常相似。 唯一真正的区别是网格视图将代理放在二维网格中,而不是线性列表中。
与列表视图相比,网格视图不依赖于它的代理的间距和大小。 相反,它使用cellWidth
和cellHeight
属性来控制内容代理的尺寸。 然后将每个代理项放置在每个此类单元格的左上角。
import QtQuick
import "../common"
Background {
width: 220
height: 300
GridView {
id: view
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
cellWidth: 45
cellHeight: 45
delegate: GreenBox {
required property int index
width: 40
height: 40
text: index
}
}
}
GridView
包含页眉和页脚、可以使用高亮代理、并支持对齐模式、以及各种边界行为。 它也可以在不同的方向和方向上定向。它也可以在控制布局元素方向(directions)和内容元素的填充方向(orientations)。
使用flow
属性控制内容的填充方向。 它可以设置为GridView.LeftToRight
或GridView.TopToBottom
。 GridView.LeftToRight
是从左到右填充网格,从上到下添加行,视图可在垂直方向滚动。 GridView.TopToBottom
从上到下添加项,从左到右填充视图,在这种情况下,滚动方向是水平的。
除了flow
属性,layoutDirection
属性可以根据使用的值将网格的方向适配从左到右(left-to-right)或从右到左(right-to-left)的语言方向。