#(85):動態視圖
`Repeater`適用于少量的靜態數據集。但是在實際應用中,數據模型往往是非常復雜的,并且數量巨大。這種情況下,`Repeater`并不十分適合。于是,QtQuick 提供了兩個專門的視圖元素:`ListView`和`GridView`。這兩個元素都繼承自`Flickable`,因此允許用戶在一個很大的數據集中進行移動。同時,`ListView`和`GridView`能夠復用創建的代理,這意味著,`ListView`和`GridView`不需要為每一個數據創建一個單獨的代理。這種技術減少了大量代理的創建造成的內存問題。
由于`ListView`和`GridView`在使用上非常相似,因此我們以`ListView`為例進行介紹。
`ListView`類似[前面章節](http://www.devbean.net/2014/06/qt-study-road-2-qml-repeater/)提到的`Repeater`元素。`ListView`使用模型提供數據,創建代理渲染數據。下面是`ListView`的簡單使用:
~~~
import QtQuick 2.2
Rectangle {
width: 80
height: 300
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: numberDelegate
spacing: 5
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
代碼運行結果如下圖所示:
[](http://files.devbean.net/images/2014/07/listview-demo.png)
如果數據模型包含的數據不能在一屏顯示完全,`ListView`只會顯示整個列表的一部分。但是,作為 QtQuick 的一種默認行為,`ListView`并不能限制顯示范圍就在代理顯示的區域內。這意味著,代理可能會在`ListView`的外部顯示出來。為避免這一點,我們需要設置`clip`屬性為`true`,使得超出`ListView`邊界的代理能夠被裁減掉。注意下圖所示的行為(左面是設置了`clip`的`ListView`而右圖則沒有):
[](http://files.devbean.net/images/2014/07/listview-clip.png)
對于用戶而言,`ListView`是一個可滾動的區域。`ListView`支持平滑滾動,這意味著它能夠快速流暢地進行滾動。默認情況下,這種滾動具有在向下到達底部時會有一個反彈的特效。這一行為由`boundsBehavior`屬性控制。`boundsBehavior`屬性有三個可選值:`Flickable.StopAtBounds`完全消除反彈效果;`Flickable.DragOverBounds`在自由滑動時沒有反彈效果,但是允許用戶拖動越界;`Flickable.DragAndOvershootBounds`則是默認值,意味著不僅用戶可以拖動越界,還可以通過自由滑動越界。
當列表滑動結束時,列表可能停在任意位置:一個代理可能只顯示一部分,另外部分被裁減掉。這一行為是由`snapMode`屬性控制的。`snapMode`屬性的默認值是`ListView.NoSnap`,也就是可以停在任意位置;`ListView.SnapToItem`會在某一代理的頂部停止滑動;`ListView.SnapOneItem`則規定每次滑動時不得超過一個代理,也就是每次只滑動一個代理,這種行為在分頁滾動時尤其有效。
默認情況下,列表視圖是縱向的。通過`orientation`屬性可以將其改為橫向。屬性可接受值為`ListView.Vertical`或`ListView.Horizontal`。例如下面的代碼:
~~~
import QtQuick 2.2
Rectangle {
width: 480
height: 80
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
orientation: ListView.Horizontal
delegate: numberDelegate
spacing: 5
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
當列表視圖橫向排列時,其中的元素按照從左向右的順序布局。使用`layoutDirection`屬性可以修改這一設置。該屬性的可選值為`Qt.LeftToRight`或`Qt.RightToLeft`。
在觸摸屏環境下使用`ListView`,默認的設置已經足夠。但是,如果在帶有鍵盤的環境下,使用方向鍵一般應該突出顯示當前項。這一特性在 QML 中稱為“高亮”。與普通的代理類似,視圖也支持使用一個專門用于高亮的代理。這可以認為是一個額外的代理,只會被實例化一次,并且只會移動到當前項目的位置。
下面的例子設置了兩個屬性。第一,`focus`屬性應當被設置為`true`,這允許`ListView`接收鍵盤焦點。第二,`highlight`屬性被設置為一個被使用的高亮代理。這個高亮代理可以使用當前項目的`x`、`y`和`height`屬性;另外,如果沒有指定`width`屬性,也可以使用當前項目的`width`屬性。在這個例子中,寬度是由`ListView.view.width`附加屬性提供的。我們會在后面的內容詳細介紹這個附加屬性。
~~~
import QtQuick 2.2
Rectangle {
width: 240
height: 300
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
delegate: numberDelegate
spacing: 5
highlight: highlightComponent
focus: true
}
Component {
id: highlightComponent
Rectangle {
width: ListView.view.width
color: "lightGreen"
}
}
Component {
id: numberDelegate
Item {
width: 40
height: 40
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
運行結果如下圖所示:
[](http://files.devbean.net/images/2014/10/highlight-delegate.png)
在使用高亮時,QML 提供了很多屬性,用于控制高亮的行為。例如,`highlightRangeMode`設置高亮如何在視圖進行顯示。默認值`ListView.NoHighlightRange`意味著高亮區域和項目的可視范圍沒有關聯;`ListView.StrictlyEnforceRange`則使高亮始終可見,如果用戶試圖將高亮區域從視圖的可視區域移開,當前項目也會隨之改變,以便保證高亮區域始終可見;介于二者之間的是`ListView.ApplyRange`,它會保持高亮區域可視,但是并不強制,也就是說,如果必要的話,高亮區域也會被移出視圖的可視區。
默認情況下,高亮的移動是由視圖負責的。這個移動速度和大小的改變都是可控的,相關屬性有`highlightMoveSpeed`,`highlightMoveDuration`,`highlightResizeSpeed`以及`highlightResizeDuration`。其中,速度默認為每秒 400 像素;持續時間被設置為 -1,意味著持續時間由速度和距離控制。同時設置速度和持續時間則由系統選擇二者中較快的那個值。有關高亮更詳細的設置則可以通過將`highlightFollowCurrentItem`屬性設置為`false`達到。這表示視圖將不再負責高亮的移動,完全交給開發者處理。下面的例子中,高亮代理的`y`屬性被綁定到`ListView.view.currentItem.y`附加屬性。這保證了高亮能夠跟隨當前項目。但是,我們不希望視圖移動高亮,而是由自己完全控制,因此在`y`屬性上面應用了一個`Behavior`。下面的代碼將這個移動的過程分成三步:淡出、移動、淡入。注意,`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 }
}
}
Rectangle {
id: highlightRectangle
anchors.fill: parent
color: "lightGreen"
}
}
}
~~~
最后需要介紹的是`ListView`的 header 和 footer。header 和 footer 可以認為是兩個特殊的代理。雖然取名為 header 和 footer,但是這兩個部分實際會添加在第一個元素之前和最后一個元素之后。也就是說,對于一個從左到右的橫向列表,header 會出現在最左側而不是上方。下面的例子演示了 header 和 footer 的位置。header 和 footer 通常用于顯示額外的元素,例如在最底部顯示“加載更多”的按鈕。
~~~
import QtQuick 2.2
Rectangle {
width: 80
height: 300
color: "white"
ListView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 4
delegate: numberDelegate
spacing: 5
header: headerComponent
footer: footerComponent
}
Component {
id: headerComponent
Rectangle {
width: 40
height: 20
color: "yellow"
}
}
Component {
id: footerComponent
Rectangle {
width: 40
height: 20
color: "red"
}
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
[](http://files.devbean.net/images/2014/10/listview-header-footer.png)
需要注意的是,header 和 footer 與`ListView`之間沒有預留間距。這意味著,header 和 footer 將緊貼著列表的第一個和最后一個元素。如果需要在二者之間留有一定的間距,則這個間距應該成為 header 和 footer 的一部分。
`GridView`與`ListView`非常相似,唯一的區別在于,`ListView`用于顯示一維列表,`GridView`則用于顯示二維表格。相比列表,表格的元素并不依賴于代理的大小和代理之間的間隔,而是由`cellWidth`和`cellHeight`屬性控制一個單元格。每一個代理都會被放置在這個單元格的左上角。
~~~
import QtQuick 2.2
Rectangle {
width: 240
height: 300
color: "white"
GridView {
anchors.fill: parent
anchors.margins: 20
clip: true
model: 100
cellWidth: 45
cellHeight: 45
delegate: numberDelegate
}
Component {
id: numberDelegate
Rectangle {
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: index
}
}
}
}
~~~
[](http://files.devbean.net/images/2014/10/gridview-demo.png)
與`ListView`類似,`GridView`也可以設置 header 和 footer,也能夠使用高亮代理和類似列表的邊界行為。`GridView`支持不同的顯示方向,這需要使用`flow`屬性控制,可選值為`GridView.LeftToRight`和`GridView.TopToBottom`。前者按照先從左向右、再從上到下的順序填充,滾動條出現在豎直方向;后者按照先從上到下、在從左到右的順序填充,滾動條出現在水平方向。
- (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(續)