6.5 视图知识进阶
6.5.1 PathView
PathView 元素是 Qt Quick 中最强大的,也是最复杂的视图。它可以创建一个视图,其中项目沿任意路径布置。沿着相同的路径,可以详细地控制尺度,不透明度等更多的属性。
使用 PathView 时,必须定义一个代理和一个路径。 除此之外,PathView 本身可以通过一系列属性进行自定义。 最常见的是 pathItemCount,一次控制可见项目的数量,高亮范围控制属性 preferredHighlightBegin,preferredHighlightEnd 和 highlightRangeMode,控制沿着当前项目的路径显示的位置。
在深入了解高亮范围控制属性之前,我们必须先看一下 path 属性。 path 属性希望在 PathView 正在滚动时定义代理遵循的路径的 Path 元素。该路径使用与路径元素(如 PathLine,PathQuad 和 PathCubic)组合的 startX 和 startY 属性来定义。 这些元素连接在一起以形成二维路径。
当定义了路径时,可以使用 PathPercent 和 PathAttribute 元素进一步调整路径。这些放置在路径元素之间,并为路径和代理提供了更精细的粒度控制。PathPercent 控制在每个元素之间覆盖的路径的一部分的大小。反过来,这样做可以控制沿途的代理分布,因为它们按比例分配到进度的百分比。
PathView 的 preferredHighlightBegin 和 preferredHighlightEnd 属性是进入视图的位置属性。他们都期望在 0 和 1 之间的范围内的小数值。预期结束位置也将或多或少等于预期的起始位置。将这两个属性设置为例如 0.5,当前项目将显示在该路径的百分之五十的位置。
在 Path 中,PathAttribute 元素放置在元素之间,就像 PathPercent 元素一样。它们允许您指定沿路径插值的属性值。这些属性附加到代理,并可用于控制任何可想到的属性。
下面的示例演示了如何使用 PathView 元素创建用户可以翻转的卡片的视图。它采取了一些技巧来做到这一点。路径由三个 PathLine 元素组成。使用 PathPercent 元素,中心元素正好居中,并提供足够的空间,不被其他元素遮挡。使用 PathAttribute 元素,可以控制旋转,大小和 z 值等属性。
除路径之外,PathView 的 pathItemCount 属性已设置。这样可以控制路径上的可视元素密度。 preferredHighlightBegin、preferredHighlightEnd 和 PathView.onPath 用于控制代理的可见性。
PathView {
anchors.fill: parent
delegate: flipCardDelegate
model: 100
path: Path {
startX: root.width/2
startY: 0
PathAttribute { name: "itemZ"; value: 0 }
PathAttribute { name: "itemAngle"; value: -90.0; }
PathAttribute { name: "itemScale"; value: 0.5; }
PathLine { x: root.width/2; y: root.height*0.4; }
PathPercent { value: 0.48; }
PathLine { x: root.width/2; y: root.height*0.5; }
PathAttribute { name: "itemAngle"; value: 0.0; }
PathAttribute { name: "itemScale"; value: 1.0; }
PathAttribute { name: "itemZ"; value: 100 }
PathLine { x: root.width/2; y: root.height*0.6; }
PathPercent { value: 0.52; }
PathLine { x: root.width/2; y: root.height; }
PathAttribute { name: "itemAngle"; value: 90.0; }
PathAttribute { name: "itemScale"; value: 0.5; }
PathAttribute { name: "itemZ"; value: 0 }
}
pathItemCount: 16
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
}
下面显示的代理利用了 PathAttribute 元素中附加的属性 itemZ,itemAngle 和itemScale。值得注意的是,代理的附加属性只能从 wrapper 中获得。因此,rotX 属性被定义为能够从 Rotation 元素内访问该值。
PathView 值得注意的另一个细节是使用附加的 PathView.onPath 属性。将可见性绑定到此是常见的做法,因为这允许 PathView 为隐藏目的保留不可见的元素。通常不能通过裁剪来处理这个问题,因为 PathView 的项目代理放置得比 ListView 或 GridView 视图的项目代理更自由。
Component {
id: flipCardDelegate
BlueBox {
id: wrapper
width: 64
height: 64
antialiasing: true
gradient: Gradient {
GradientStop { position: 0.0; color: "#2ed5fa" }
GradientStop { position: 1.0; color: "#2467ec" }
}
visible: PathView.onPath
scale: PathView.itemScale
z: PathView.itemZ
property variant rotX: PathView.itemAngle
transform: Rotation {
axis { x: 1; y: 0; z: 0 }
angle: wrapper.rotX;
origin { x: 32; y: 32; }
}
text: index
}
}
当在 PathView 中转换图像或其他复杂元素时,常用的性能优化技巧是将 Image 元素的平滑(smooth)属性绑定到附加属性 PathView.view.moving。 这意味着图像在移动时不那么平滑漂亮,但在静止时使图像平滑。 当视图处于运动状态时,无需花费处理能力来使图像平滑,因为用户将无法看到这一点。
6.5.2 从 XML 获取模型数据
由于 XML 是无处不在的数据格式,QML 提供了将 XML 数据作为模型的 XmlListModel 元素。该元素可以从本地或远程获取 XML 数据,然后使用 XPath 表达式处理数据。
下面的例子演示从 RSS 流中获取图像。源属性是指通过 HTTP 的删除位置,数据自动下载。
当数据被下载时,它被处理成模型项目和角色。query 属性是表示创建模型项目的基本查询的 XPath。在此示例中,路径为 /rss/channel/item,因此对于 RSS 标签内的通道标签内的每个项目标签,都会创建一个模型项目。
对于每个模型项目,都会提取多个角色。这些由 XmlRole 元素表示。给每个角色一个名称,代表可以通过附加的属性访问。通过每个角色的 XPath 查询来确定每个这样的属性的实际值。例如,title 属性对应于 title/string() 查询,返回 <title> 和 </ title> 标签之间的内容。
imageSource 属性更有趣,因为它不仅从 XML 中提取字符串,还可以处理它。在提供的流中,每个项目都包含一个由 <img src= 标签表示的图像。使用 XPath 的函数 substring-after 和 substring-before,提取并返回图像的位置。因此,imageSource 属性可以直接用作 Image 元素的源(source)。
import QtQuick 2.5
import QtQuick.XmlListModel 2.0
import "../common"
Background {
width: 300
height: 480
Component {
id: imageDelegate
Box {
width: listView.width
height: 220
color: '#333'
Column {
Text {
text: title
color: '#e0e0e0'
}
Image {
width: listView.width
height: 200
fillMode: Image.PreserveAspectCrop
source: imageSource
}
}
}
}
XmlListModel {
id: imageModel
source: "http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
XmlRole { name: "imageSource"; query: "substring-before(substring-after(description/string(), 'img src=\"'), '\"')" }
}
ListView {
id: listView
anchors.fill: parent
model: imageModel
delegate: imageDelegate
}
}
6.5.3 列表的章节功能
有时,列表中的数据可以分为几个部分。可以将联系人列表分成类似专辑下的字母表或音乐曲目的每个字母下的部分的形式。使用 ListView 可以将普通的列表分成几个类别,从而为查阅提供更多的深度。
为了使用 section,必须设置 section.property 和 section.criteria。section.property 定义了哪些属性用于将内容分成几个部分。这里,需要注意的是要知道必须对模型进行排序,以便每个部分由连续元素组成,否则相同的属性名称可能会显示在多个位置。
section.criteria 可以设置为 ViewSection.FullString 或 ViewSection.FirstCharacter。第一个是默认值,可用于具有清晰部分的模型,例如音乐专辑的曲目。后者采用属性的第一个字符,意味着可以使用任何属性。最常见的例子是电话簿中联系人的姓氏。
当定义了这些部分时,可以使用附加的属性 ListView.section,ListView.previousSection 和 ListView.nextSection 从每个项目访问它们。使用这些属性,可以检测一个部分的第一个和最后一个项目,并相应地进行操作。
也可以将部分代理组件分配给 ListView 的 section.delegate 属性。这将创建一个标题代理,它插入到节的任何项目之前。代理组件可以使用附加属性 section 访问当前标题的名称。
下面的例子通过显示在他们的国籍之后划分的空间男子列表来演示该节的概念。nation 被用作 section.property。section.delegate 组件 sectionDelegate 显示每个国家的标题,显示国家的名称。在每个章节中,使用 spaceManDelegate 组件显示空间的名称。
import QtQuick 2.5
import "../common"
Background {
width: 300
height: 290
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: spaceMen
delegate: spaceManDelegate
section.property: "nation"
section.delegate: sectionDelegate
}
Component {
id: spaceManDelegate
Item {
width: ListView.view.width
height: 20
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
font.pixelSize: 12
text: name
color: '#1f1f1f'
}
}
}
Component {
id: sectionDelegate
BlueBox {
width: ListView.view.width
height: 20
text: section
fontColor: '#e0e0e0'
}
}
ListModel {
id: spaceMen
ListElement { name: "Abdul Ahad Mohmand"; nation: "Afganistan"; }
ListElement { name: "Marcos Pontes"; nation: "Brazil"; }
ListElement { name: "Alexandar Panayotov Alexandrov"; nation: "Bulgaria"; }
ListElement { name: "Georgi Ivanov"; nation: "Bulgaria"; }
ListElement { name: "Roberta Bondar"; nation: "Canada"; }
ListElement { name: "Marc Garneau"; nation: "Canada"; }
ListElement { name: "Chris Hadfield"; nation: "Canada"; }
ListElement { name: "Guy Laliberte"; nation: "Canada"; }
ListElement { name: "Steven MacLean"; nation: "Canada"; }
ListElement { name: "Julie Payette"; nation: "Canada"; }
ListElement { name: "Robert Thirsk"; nation: "Canada"; }
ListElement { name: "Bjarni Tryggvason"; nation: "Canada"; }
ListElement { name: "Dafydd Williams"; nation: "Canada"; }
}
}
6.5.4 调整性能
模型视图的可感知的性能差别在很大程度上取决于准备新代理所需的时间。例如,当通过 ListView 向下滚动时,代理将添加到底部的视图下面,并且在视图顶部视图中被移除。如果 clip 属性设置为 false,这个过程将变得更加明显。如果代理花费太多的时间进行初始化,一旦视图滚动得太快,用户就会感知到性能上的卡顿。
要解决此问题,我们可以调整滚动视图四周的边距(以像素为单位)。这是使用 cacheBuffer 属性完成的。在上述情况下,垂直滚动,它将控制将包含准备代理的 ListView 上下几个像素。例如,将它与异步加载的图像元素相结合可以在图像被加入视图之前给予图像加载时间。
让更多的代理牺牲内存以获得更平滑的体验,同时会消耗更多的时间来初始化每个代理。这不能解决复杂代理的问题。每次代理被实例化,它的内容被解析和编译。这需要时间,如果在解析和编译时花费太多时间,将导致滚动的体验不佳。在代理中加入了太多的元素也会降低滚动性能,因为这将导致花费更多的时间去移动更多的元素。
要解决后面的两个问题,建议使用 Loader 元素。这些可以用于在需要时再去实例化的其他元素。例如,扩展代理可以使用 Loader 来推迟其真正视图的实例化,直到需要时再加载真正的视图。由于同样的原因,每个代理中将 JavaScript 脚本的调用数量保持在最小值是很好的。最好让他们调用驻留在每个代理之外的元素(一般是相应的视图)中定义的 JavaScript 脚本。这样就减少了每次创建代理时解析和编译 JavaScript 脚本所花费的时间。
6.6 总结
在本章中,我们研究了模型,视图和代理。对于模型中的每个数据条目,视图实例化可视化数据的代理。这实现了数据与视图的分离。
模型可以是单个整数,其中索引变量提供给代理。如果使用 JavaScript 数组作为模型,则 modelData 变量表示数组当前索引的数据,而 index 仍然会保留用作索引。对于更复杂的情况,每个数据项需要提供多个值,则使用 ListElement 项填充的 ListModel 是一个更好的解决方案。
对于静态模型,可以使用 Repeater 作为视图。将其与定位器,如行(Row)、列(Column)、网格(Grid )或流(Flow),组合很容易,以构建用户界面部件。对于动态或大型数据模型,ListView 或 GridView 等视图更合适。它们根据需要在运行中创建代理实例,可以减少场景中的实际显示的元素数量。
视图中使用的代理可以是具有绑定到模型数据的属性的静态项,或者它们可以是动态的,状态取决于它们是否在焦点。使用该视图的附加 onAdd 和 onRemove 信号,可以在代理内容显示和消失添加动画效果。