#(87):模型-視圖高級技術
## PathView
`PathView`是 QtQuick 中最強大的視圖,同時也是最復雜的。`PathView`允許創建一種更靈活的視圖。在這種視圖中,數據項并不是方方正正,而是可以沿著任意路徑布局。沿著同一布局路徑,數據項的屬性可以被更詳細的設置,例如縮放、透明度等。
使用`PathView`首先需要定義一個代理和一個路徑。除此之外,`PathView`還可以設置很多其它屬性,其中最普遍的是`pathItemCount`,用于設置可視數據項的數目;`preferredHighlightBegin`、`preferredHighlightEnd`和`highlightRangeMode`可以設置高亮的范圍,也就是沿著路徑上面的當前可以被顯示的數據項。
在深入了解高亮范圍之前,我們必須首先了解`path`屬性。`path`接受一個`Path`元素,用于定義`PathView`中的代理所需要的路徑。該路徑使用`startX`和`startY`屬性,結合`PathLine`、`PathQuad`、`PathCubic`等路徑元素進行定義。這些元素可以結合起來形成一個二維路徑。
一旦路徑定義完成,我們可以使用`PathPercent`和`PathAttribute`元素進行調整。這些元素用于兩個路徑元素之間,更好的控制路徑和路徑上面的代理。`PathPercent`控制兩個元素之間的路徑部分有多大。它控制了路徑上面代理的分布,這些代理按照其定義的百分比進行分布。
`PathAttribute`元素同`PathPercent`同樣放置在元素之間。該元素允許沿路徑插入一些屬性值。這些屬性值附加到代理上面,可用于任何能夠使用的屬性。
下面的例子演示了如何利用`PathView`實現卡片的彈入。這里使用了一些技巧來達到這一目的。它的路徑包含三個`PathLine`元素。通過`PathPercent`元素,中間的元素可以正好位于中央位置,并且能夠留有充足的空間,以避免被別的元素遮擋。元素的旋轉、大小縮放和 Z 軸都是由`PathAttribute`進行控制。除了定義路徑,我們還設置了`PathView`的`pathItemCount`屬性。該屬性用于指定路徑所期望的元素個數。最后,代理中的`PathView.onPath`使用`preferredHighlightBegin`和`preferredHighlightEnd`屬性控制代理的可見性。
~~~
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.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
}
}
~~~
示例運行結果如下:
[](http://files.devbean.net/images/2015/09/pathview.png)
完成`PathView`中圖片和一些復雜元素的變換之后,通常會進行一定的性能優化,比如,將`Image`元素的`smooth`屬性綁定到`PathView.view.moving`附加屬性。這意味著在移動時,圖片質量會稍有下降,靜止時恢復正常。在視圖移動時,很少有用戶會在意圖片的清晰度,因此,這樣的妥協一般是可以接受的。
## 從 XML 加載模型
XML 是一種非常常見的數據格式,QML 提供了`XmlListModel`元素支持將 XML 數據轉換為模型。`XmlListModel`可以加載本地或遠程的 XML 文檔,使用 XPath 表達式處理數據。
下面的例子給出了如何從 RSS 獲取圖片。`source`屬性指向了一個遠程地址,其數據會被自動下載下來。
~~~
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
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://www.padmag.cn/feed"
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
}
}
~~~
當數據被下載下來,這個 XML 就被處理成模型的數據項和角色。`query`屬性是 XPath 表達式語言,用于創建模型數據項。在這個例子中,該屬性值為`/rss/channel/item`,因此,rss 標簽下的每一個 channel 標簽中的每一個 item 標簽,都會被生成一個數據項。每一個數據項都可以定義一系列角色,這些角色使用`XmlRole`表示。每一個角色都有一個名字,代理可以使用附件屬性訪問到其值。角色的值是使用 XPath 表達式獲取的。例如,`title`屬性的值由`title/string()`表達式決定,返回的是`<title>`和`</title>`標簽之間的文本。`imageSource`屬性值則更有趣。它并不是直接由 XML 獲取的字符串,而是一系列函數的運算結果。在返回的 XML 中,有些 item 中包含圖片,使用`<img src=`標簽表示。使用`substring-after`和`substring-before`XPath 函數,可以找到每張圖片的地址并返回。因此,`imageSource`屬性可以直接作為`Image`元素的`source`屬性值。
## 分組列表
有時,列表中的數據可以分成幾個部分,例如,按照列表數據的首字母分組。利用`ListView`可以將一個扁平的列表分為幾個組,如下圖所示:
[](http://files.devbean.net/images/2015/09/section.png)
為了使用分組,需要設置`section.property`和`section.criteria`兩個屬性。`section.property`定義了使用哪個屬性進行分組。這里,需要確保模型已經排好序了,以便每一部分能夠包含連續的元素,否則,同一屬性的名字可能出現在多個位置。`section.criteria`的可選值為`ViewSection.FullString`或`ViewSection.FirstCharacter`。前者為默認值,適用于具有明顯分組的模型,例如,音樂集等;后者按照屬性首字母分組,并且意味著所有屬性都適用于此,常見例子是電話本的通訊錄名單。
一旦分組定義完畢,在每一個數據項就可以使用附加屬性`ListView.section`、`ListView.previousSection`和`ListView.nextSection`訪問到這個分組。使用這個屬性,我們就可以找到一個分組的第一個和最后一個元素,從而實現某些特殊功能。
我們也可以給`ListView`的`section.delegate`屬性賦值,以便自定義分組顯示的代理。這會在一個組的數據項之前插入一個用于顯示分組的代理。這個代理可以使用附加屬性訪問當前分組的名字。
下面的例子按照國別對一組人進行分組。國別被設置為`section.property`屬性的值。`section.delegate`組件,也就是`sectionDelegate`,用于顯示每組的名字,也就是國家名。每組中的人名則使用`spaceManDelegate`顯示。
~~~
import QtQuick 2.0
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"; }
}
}
~~~
## 關于性能
模型視圖的性能很大程度上取決于創建新的代理所造成的消耗。例如,如果`clip`屬性設置為`false`,當向下滾動`ListView`時,系統會在列表末尾創建新的代理,并且將列表上部已經不可顯示的代理移除。顯然,當初始化代理需要消耗大量時間時,用戶在快速拖動滾動條時,這種現象就會造成一定程度的影響。
為了避免這種情況,你可以調整被滾動視圖的外邊框的值。通過修改`cacheBuffer`屬性即可達到這一目的。在上面所述的有關豎直滾動的例子中,這個屬性會影響到列表上方和下方會有多少像素。這些像素則影響到是否能夠容納這些代理。例如,將異步加載圖片與此結合,就可以實現在圖片真正加載完畢之后才顯示出來。
更多的代理意味著更多的內存消耗,從而影響到用戶的操作流暢度,同時也有關代理初始化的時間。對于復雜的代理,上面的方法并不能從根本上解決問題。代理初始化一次,其內容就會被重新計算。這會消耗時間,如果這個時間很長,很顯然,這會降低用戶體驗。代理中子元素的個數同樣也有影響。原因很簡單,移動更多的元素當然要更多的時間。為了解決前面所說的兩個問題,我們推薦使用`Loader`元素。`Loader`元素允許延時加載額外的元素。例如,一個可展開的代理,只有當用戶點擊時,才會顯示這一項的詳細信息,包含一個很大的圖片。那么,利用`Loader`元素,我們可以做到,只有其被顯示時才進行加載,否則不加載。基于同樣的原因,應該使每個代理中包含的 JavaScript 代碼盡可能少。最好能做到在代理之外調用復雜的 JavaScript 代碼。這將減少代理創建時編譯 JavaScript 所消耗的時間。
**附件**
1. [pathview.zip](http://files.devbean.net/code/pathview.zip)
2. [xmllistmodel.zip](http://files.devbean.net/code/xmllistmodel.zip)
3. [section.zip](http://files.devbean.net/code/section.zip)
- (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(續)