6.3 动态视图
Repeater 适用于有限和静态数据集,但在现实世界中,模型通常更复杂和更大。 在这里,需要一个更智能的解决方案。为此,Qt Quick 提供了 ListView 和 GridView 元素。 这些都是基于 Flickable 的元素,因此用户可以在较大的数据集中滑动查看视图中的内容。 同时,它们限制了同时实例化的 delegate 的数量。对于大型的数据模型,这意味着场景中一次加载的元素将变得更少。
两个元素的用法相似。 因此,我们将从 ListView 开始,然后再介绍 GridView,前者相对比较基础。ListView 类似于 Repeater元素。 它使用一个模型,实例化一个delegate 并且在代理展示的内容之间,可以有间距(spacing)属性。下面的列表显示了一个简单的设置如何实现这些。
import QtQuick 2.5
import "../common"
Background {
width: 80
height: 300
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: numberDelegate
spacing: 5
}
Component {
id: numberDelegate
GreenBox {
width: 40
height: 40
text: index
}
}
}
如果模型包含的数据超出了屏幕窗口可以用于展示数据的范围,则 ListView 将仅显示列表项中的一部分。但是,由于 Qt Quick 的默认行为,列表视图不会限制显示代理的区域。 这意味着代理可能在列表视图之外可见,并且在列表视图之外的代理的动态创建和销毁对用户是可见的。为了防止这种情况,必须通过将 clip 属性设置为true 的方式在 ListView 元素上激活裁剪。下图显示了与 clip 属性默认为 false 时相比较的结果。
对于用户来说,ListView 是一个可滚动区域。它支持动态滚动,这意味着它可以快速滑动屏幕实现快速移动内容的目的。默认情况下,它也可以通过到达视图内容结束位置的回弹效果,使用户意识到已经到达视图的结束位置。
视图滚动结束时的行为是使用 boundsBehavior 属性控制的。这是一个枚举值,可以配置默认值 Flickable.DragAndOvershootBounds,这意味着的视图可以在其边界之外拖动并自动回弹到视图结束位置,值 Flickable.StopAtBounds 则意味着该视图永远不会移动到其边界之外。介于以上两者之间的值,Flickable.DragOverBounds,让用户可以拖动视图超出其边界,但轻击将返回视图结束位置。
可以限制视图被允许停止的位置。 这是使用 snapMode 属性控制的。 默认行为 ListView.NoSnap 允许视图在任何位置停止。通过将 snapMode 属性设置为 ListView.SnapToItem,视图将始终将项的顶部与其顶部对齐。 最后,ListView.SnapOneItem,当鼠标按钮或触摸被释放时,视图将从第一个可见项目停止不超过一个项目。 最后一个模式在翻页时非常方便。
6.3.1 视图方向
列表视图默认提供了一个垂直滚动列表,但是水平滚动有时也同样有用。列表视图的方向通过 orientation 属性进行控制。 它可以设置为默认值 ListView.Vertical 或 ListView.Horizontal。水平列表视图如下所示。
import QtQuick 2.5
import "../common"
Background {
width: 480
height: 80
ListView {
anchors.fill: parent
anchors.margins: 20
spacing: 4
clip: true
model: 100
orientation: ListView.Horizontal
delegate: numberDelegate
}
Component {
id: numberDelegate
GreenBox {
width: 40
height: 40
text: index
}
}
}
如你所知,水平方向默认从左到右流动。 这可以通过 layoutDirection 属性进行控制,该属性可以设置为 Qt.LeftToRight 或 Qt.RightToLeft,具体取决于视图方向。
6.3.2 键盘导航和高亮
在基于触摸的设置中使用 ListView 时,视图本身就足够了。 在具有键盘的场景中,或者甚至只需用箭头键选择项目,就需要一个指示当前项目的机制。 在 QML 中,这称为高亮显示。
视图支持与代理一起显示在视图中的高亮代理 。它可以被认为是一个额外的代理,但是它只被实例化一次,并被移动到与当前项目相同的位置显示。
下面的例子,将用于展示上述内容。有两个属性涉及这个工作。首先,focus 属性要设置为 true。这给了 ListView 的键盘焦点。其次,highlight 属性设置为指出要使用的高亮代理。高亮代理被赋予当前项的 x,y 和 height 值。如果未指定 width,则也使用当前项的宽度。
在示例中,ListView.view.width 附加属性用于 width 值。代理可以使用的附加属性将在本章的代理部分进一步讨论,但是很高兴我们可以提前知道同样的属性也可以用于高亮代理之中。
import QtQuick 2.5
import "../common"
Background {
width: 240
height: 300
ListView {
id: view
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: numberDelegate
spacing: 5
highlight: highlightComponent
focus: true
}
Component {
id: highlightComponent
GreenBox {
width: ListView.view.width
}
}
Component {
id: numberDelegate
Item {
width: ListView.view.width
height: 40
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: 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 的方法完成的。在下面的例子中,移动分为三个步骤:淡出,移动,淡入。请注意 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 }
}
}
GreenBox {
id: highlightRectangle
anchors.fill: parent
}
}
}
6.3.3 页眉和页脚属性
在 ListView 内容的最后,可以介绍一下 header 和 footer 属性。这些可以被认为是列表开头或末尾的特殊代理位置。对于水平列表,这些不会出现在头部或尾部,而是在开始或结束时显示,具体取决于所使用的 layoutDirection 属性的值。
下面的示例说明了如何使用页眉和页脚来增强对列表的开始和结束的感知。这些特殊列表元素还有其他用途。例如,它们可以用于安放一个用于加载更多的内容的按钮。
import QtQuick 2.5
import "../common"
Background {
width: 240
height: 300
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 4
delegate: numberDelegate
spacing: 2
header: headerComponent
footer: footerComponent
}
Component {
id: headerComponent
YellowBox {
width: ListView.view.width
height: 20
text: 'Header'
}
}
Component {
id: footerComponent
YellowBox {
width: ListView.view.width
height: 20
text: 'Footer'
}
}
Component {
id: numberDelegate
GreenBox {
width: ListView.view.width
height: 40
text: 'Item #' + index
}
}
}
** 注意: **
页眉和页脚代理不会实现 ListView 的 spacing 属性,而是紧挨着显示在列表中的代理。这意味着如果有必要我们必须自己来实现页眉和页脚项的 spacing 的效果。
6.3.4 网格视图
使用 GridView 非常类似于使用 ListView。唯一的区别是网格视图将代理放置在二维网格而不是线性列表中。
与列表视图相比,网格视图不依赖于间距和其代理的大小。相反,它使用 cellWidth 和 cellHeight 属性来控制代理内容的大小。然后,默认情况下每个代理项目都放在每个这样的单元格的左上角。
import QtQuick 2.5
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: numberDelegate
}
Component {
id: numberDelegate
GreenBox {
width: 40
height: 40
text: index
}
}
}
GridView 包含页眉和页脚,可以使用高亮显示代理,并支持快照模式以及各种边界行为。它也可以在不同的方向和方向上定向。
使用 flow 属性控制网格视图内的元素的方向。它可以设置为 GridView.LeftToRight 或 GridView.TopToBottom。 前一个值从左到右填充网格,从顶部到底部添加行。 视图可在垂直方向上滚动。 后一个值从顶部到底部添加项目,从左到右填充视图。 在这种情况下,滚动方向是水平的。
除了 flow 属性之外,layoutDirection 属性可以根据所使用的值,将网格的方向调整为从左到右或从右到左的视图。