6.5 視圖知識進(jìn)階
6.5.1 PathView
PathView 元素是 Qt Quick 中最強(qiáng)大的,也是最復(fù)雜的視圖。它可以創(chuàng)建一個視圖,其中項目沿任意路徑布置。沿著相同的路徑,可以詳細(xì)地控制尺度,不透明度等更多的屬性。
使用 PathView 時,必須定義一個代理和一個路徑。 除此之外,PathView 本身可以通過一系列屬性進(jìn)行自定義。 最常見的是 pathItemCount,一次控制可見項目的數(shù)量,高亮范圍控制屬性 preferredHighlightBegin,preferredHighlightEnd 和 highlightRangeMode,控制沿著當(dāng)前項目的路徑顯示的位置。
在深入了解高亮范圍控制屬性之前,我們必須先看一下 path 屬性。 path 屬性希望在 PathView 正在滾動時定義代理遵循的路徑的 Path 元素。該路徑使用與路徑元素(如 PathLine,PathQuad 和 PathCubic)組合的 startX 和 startY 屬性來定義。 這些元素連接在一起以形成二維路徑。
當(dāng)定義了路徑時,可以使用 PathPercent 和 PathAttribute 元素進(jìn)一步調(diào)整路徑。這些放置在路徑元素之間,并為路徑和代理提供了更精細(xì)的粒度控制。PathPercent 控制在每個元素之間覆蓋的路徑的一部分的大小。反過來,這樣做可以控制沿途的代理分布,因?yàn)樗鼈儼幢壤峙涞竭M(jìn)度的百分比。
PathView 的 preferredHighlightBegin 和 preferredHighlightEnd 屬性是進(jìn)入視圖的位置屬性。他們都期望在 0 和 1 之間的范圍內(nèi)的小數(shù)值。預(yù)期結(jié)束位置也將或多或少等于預(yù)期的起始位置。將這兩個屬性設(shè)置為例如 0.5,當(dāng)前項目將顯示在該路徑的百分之五十的位置。
在 Path 中,PathAttribute 元素放置在元素之間,就像 PathPercent 元素一樣。它們允許您指定沿路徑插值的屬性值。這些屬性附加到代理,并可用于控制任何可想到的屬性。

下面的示例演示了如何使用 PathView 元素創(chuàng)建用戶可以翻轉(zhuǎn)的卡片的視圖。它采取了一些技巧來做到這一點(diǎn)。路徑由三個 PathLine 元素組成。使用 PathPercent 元素,中心元素正好居中,并提供足夠的空間,不被其他元素遮擋。使用 PathAttribute 元素,可以控制旋轉(zhuǎn),大小和 z 值等屬性。
除路徑之外,PathView 的 pathItemCount 屬性已設(shè)置。這樣可以控制路徑上的可視元素密度。 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 元素內(nèi)訪問該值。
PathView 值得注意的另一個細(xì)節(jié)是使用附加的 PathView.onPath 屬性。將可見性綁定到此是常見的做法,因?yàn)檫@允許 PathView 為隱藏目的保留不可見的元素。通常不能通過裁剪來處理這個問題,因?yàn)?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
}
}
當(dāng)在 PathView 中轉(zhuǎn)換圖像或其他復(fù)雜元素時,常用的性能優(yōu)化技巧是將 Image 元素的平滑(smooth)屬性綁定到附加屬性 PathView.view.moving。 這意味著圖像在移動時不那么平滑漂亮,但在靜止時使圖像平滑。 當(dāng)視圖處于運(yùn)動狀態(tài)時,無需花費(fèi)處理能力來使圖像平滑,因?yàn)橛脩魧o法看到這一點(diǎn)。
6.5.2 從 XML 獲取模型數(shù)據(jù)
由于 XML 是無處不在的數(shù)據(jù)格式,QML 提供了將 XML 數(shù)據(jù)作為模型的 XmlListModel 元素。該元素可以從本地或遠(yuǎn)程獲取 XML 數(shù)據(jù),然后使用 XPath 表達(dá)式處理數(shù)據(jù)。
下面的例子演示從 RSS 流中獲取圖像。源屬性是指通過 HTTP 的刪除位置,數(shù)據(jù)自動下載。

當(dāng)數(shù)據(jù)被下載時,它被處理成模型項目和角色。query 屬性是表示創(chuàng)建模型項目的基本查詢的 XPath。在此示例中,路徑為 /rss/channel/item,因此對于 RSS 標(biāo)簽內(nèi)的通道標(biāo)簽內(nèi)的每個項目標(biāo)簽,都會創(chuàng)建一個模型項目。
對于每個模型項目,都會提取多個角色。這些由 XmlRole 元素表示。給每個角色一個名稱,代表可以通過附加的屬性訪問。通過每個角色的 XPath 查詢來確定每個這樣的屬性的實(shí)際值。例如,title 屬性對應(yīng)于 title/string() 查詢,返回 <title> 和 </ title> 標(biāo)簽之間的內(nèi)容。
imageSource 屬性更有趣,因?yàn)樗粌H從 XML 中提取字符串,還可以處理它。在提供的流中,每個項目都包含一個由 <img src= 標(biāo)簽表示的圖像。使用 XPath 的函數(shù) 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 列表的章節(jié)功能
有時,列表中的數(shù)據(jù)可以分為幾個部分??梢詫⒙?lián)系人列表分成類似專輯下的字母表或音樂曲目的每個字母下的部分的形式。使用 ListView 可以將普通的列表分成幾個類別,從而為查閱提供更多的深度。

為了使用 section,必須設(shè)置 section.property 和 section.criteria。section.property 定義了哪些屬性用于將內(nèi)容分成幾個部分。這里,需要注意的是要知道必須對模型進(jìn)行排序,以便每個部分由連續(xù)元素組成,否則相同的屬性名稱可能會顯示在多個位置。
section.criteria 可以設(shè)置為 ViewSection.FullString 或 ViewSection.FirstCharacter。第一個是默認(rèn)值,可用于具有清晰部分的模型,例如音樂專輯的曲目。后者采用屬性的第一個字符,意味著可以使用任何屬性。最常見的例子是電話簿中聯(lián)系人的姓氏。
當(dāng)定義了這些部分時,可以使用附加的屬性 ListView.section,ListView.previousSection 和 ListView.nextSection 從每個項目訪問它們。使用這些屬性,可以檢測一個部分的第一個和最后一個項目,并相應(yīng)地進(jìn)行操作。
也可以將部分代理組件分配給 ListView 的 section.delegate 屬性。這將創(chuàng)建一個標(biāo)題代理,它插入到節(jié)的任何項目之前。代理組件可以使用附加屬性 section 訪問當(dāng)前標(biāo)題的名稱。
下面的例子通過顯示在他們的國籍之后劃分的空間男子列表來演示該節(jié)的概念。nation 被用作 section.property。section.delegate 組件 sectionDelegate 顯示每個國家的標(biāo)題,顯示國家的名稱。在每個章節(jié)中,使用 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 調(diào)整性能
模型視圖的可感知的性能差別在很大程度上取決于準(zhǔn)備新代理所需的時間。例如,當(dāng)通過 ListView 向下滾動時,代理將添加到底部的視圖下面,并且在視圖頂部視圖中被移除。如果 clip 屬性設(shè)置為 false,這個過程將變得更加明顯。如果代理花費(fèi)太多的時間進(jìn)行初始化,一旦視圖滾動得太快,用戶就會感知到性能上的卡頓。
要解決此問題,我們可以調(diào)整滾動視圖四周的邊距(以像素為單位)。這是使用 cacheBuffer 屬性完成的。在上述情況下,垂直滾動,它將控制將包含準(zhǔn)備代理的 ListView 上下幾個像素。例如,將它與異步加載的圖像元素相結(jié)合可以在圖像被加入視圖之前給予圖像加載時間。
讓更多的代理犧牲內(nèi)存以獲得更平滑的體驗(yàn),同時會消耗更多的時間來初始化每個代理。這不能解決復(fù)雜代理的問題。每次代理被實(shí)例化,它的內(nèi)容被解析和編譯。這需要時間,如果在解析和編譯時花費(fèi)太多時間,將導(dǎo)致滾動的體驗(yàn)不佳。在代理中加入了太多的元素也會降低滾動性能,因?yàn)檫@將導(dǎo)致花費(fèi)更多的時間去移動更多的元素。
要解決后面的兩個問題,建議使用 Loader 元素。這些可以用于在需要時再去實(shí)例化的其他元素。例如,擴(kuò)展代理可以使用 Loader 來推遲其真正視圖的實(shí)例化,直到需要時再加載真正的視圖。由于同樣的原因,每個代理中將 JavaScript 腳本的調(diào)用數(shù)量保持在最小值是很好的。最好讓他們調(diào)用駐留在每個代理之外的元素(一般是相應(yīng)的視圖)中定義的 JavaScript 腳本。這樣就減少了每次創(chuàng)建代理時解析和編譯 JavaScript 腳本所花費(fèi)的時間。
6.6 總結(jié)
在本章中,我們研究了模型,視圖和代理。對于模型中的每個數(shù)據(jù)條目,視圖實(shí)例化可視化數(shù)據(jù)的代理。這實(shí)現(xiàn)了數(shù)據(jù)與視圖的分離。
模型可以是單個整數(shù),其中索引變量提供給代理。如果使用 JavaScript 數(shù)組作為模型,則 modelData 變量表示數(shù)組當(dāng)前索引的數(shù)據(jù),而 index 仍然會保留用作索引。對于更復(fù)雜的情況,每個數(shù)據(jù)項需要提供多個值,則使用 ListElement 項填充的 ListModel 是一個更好的解決方案。
對于靜態(tài)模型,可以使用 Repeater 作為視圖。將其與定位器,如行(Row)、列(Column)、網(wǎng)格(Grid )或流(Flow),組合很容易,以構(gòu)建用戶界面部件。對于動態(tài)或大型數(shù)據(jù)模型,ListView 或 GridView 等視圖更合適。它們根據(jù)需要在運(yùn)行中創(chuàng)建代理實(shí)例,可以減少場景中的實(shí)際顯示的元素數(shù)量。
視圖中使用的代理可以是具有綁定到模型數(shù)據(jù)的屬性的靜態(tài)項,或者它們可以是動態(tài)的,狀態(tài)取決于它們是否在焦點(diǎn)。使用該視圖的附加 onAdd 和 onRemove 信號,可以在代理內(nèi)容顯示和消失添加動畫效果。