6.3 動態(tài)視圖
Repeater 適用于有限和靜態(tài)數(shù)據(jù)集,但在現(xiàn)實世界中,模型通常更復(fù)雜和更大。 在這里,需要一個更智能的解決方案。為此,Qt Quick 提供了 ListView 和 GridView 元素。 這些都是基于 Flickable 的元素,因此用戶可以在較大的數(shù)據(jù)集中滑動查看視圖中的內(nèi)容。 同時,它們限制了同時實例化的 delegate 的數(shù)量。對于大型的數(shù)據(jù)模型,這意味著場景中一次加載的元素將變得更少。


兩個元素的用法相似。 因此,我們將從 ListView 開始,然后再介紹 GridView,前者相對比較基礎(chǔ)。ListView 類似于 Repeater元素。 它使用一個模型,實例化一個delegate 并且在代理展示的內(nèi)容之間,可以有間距(spacing)屬性。下面的列表顯示了一個簡單的設(shè)置如何實現(xiàn)這些。
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
}
}
}

如果模型包含的數(shù)據(jù)超出了屏幕窗口可以用于展示數(shù)據(jù)的范圍,則 ListView 將僅顯示列表項中的一部分。但是,由于 Qt Quick 的默認(rèn)行為,列表視圖不會限制顯示代理的區(qū)域。 這意味著代理可能在列表視圖之外可見,并且在列表視圖之外的代理的動態(tài)創(chuàng)建和銷毀對用戶是可見的。為了防止這種情況,必須通過將 clip 屬性設(shè)置為true 的方式在 ListView 元素上激活裁剪。下圖顯示了與 clip 屬性默認(rèn)為 false 時相比較的結(jié)果。

對于用戶來說,ListView 是一個可滾動區(qū)域。它支持動態(tài)滾動,這意味著它可以快速滑動屏幕實現(xiàn)快速移動內(nèi)容的目的。默認(rèn)情況下,它也可以通過到達視圖內(nèi)容結(jié)束位置的回彈效果,使用戶意識到已經(jīng)到達視圖的結(jié)束位置。
視圖滾動結(jié)束時的行為是使用 boundsBehavior 屬性控制的。這是一個枚舉值,可以配置默認(rèn)值 Flickable.DragAndOvershootBounds,這意味著的視圖可以在其邊界之外拖動并自動回彈到視圖結(jié)束位置,值 Flickable.StopAtBounds 則意味著該視圖永遠不會移動到其邊界之外。介于以上兩者之間的值,F(xiàn)lickable.DragOverBounds,讓用戶可以拖動視圖超出其邊界,但輕擊將返回視圖結(jié)束位置。
可以限制視圖被允許停止的位置。 這是使用 snapMode 屬性控制的。 默認(rèn)行為 ListView.NoSnap 允許視圖在任何位置停止。通過將 snapMode 屬性設(shè)置為 ListView.SnapToItem,視圖將始終將項的頂部與其頂部對齊。 最后,ListView.SnapOneItem,當(dāng)鼠標(biāo)按鈕或觸摸被釋放時,視圖將從第一個可見項目停止不超過一個項目。 最后一個模式在翻頁時非常方便。
6.3.1 視圖方向
列表視圖默認(rèn)提供了一個垂直滾動列表,但是水平滾動有時也同樣有用。列表視圖的方向通過 orientation 屬性進行控制。 它可以設(shè)置為默認(rèn)值 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
}
}
}

如你所知,水平方向默認(rèn)從左到右流動。 這可以通過 layoutDirection 屬性進行控制,該屬性可以設(shè)置為 Qt.LeftToRight 或 Qt.RightToLeft,具體取決于視圖方向。
6.3.2 鍵盤導(dǎo)航和高亮
在基于觸摸的設(shè)置中使用 ListView 時,視圖本身就足夠了。 在具有鍵盤的場景中,或者甚至只需用箭頭鍵選擇項目,就需要一個指示當(dāng)前項目的機制。 在 QML 中,這稱為高亮顯示。
視圖支持與代理一起顯示在視圖中的高亮代理 。它可以被認(rèn)為是一個額外的代理,但是它只被實例化一次,并被移動到與當(dāng)前項目相同的位置顯示。
下面的例子,將用于展示上述內(nèi)容。有兩個屬性涉及這個工作。首先,focus 屬性要設(shè)置為 true。這給了 ListView 的鍵盤焦點。其次,highlight 屬性設(shè)置為指出要使用的高亮代理。高亮代理被賦予當(dāng)前項的 x,y 和 height 值。如果未指定 width,則也使用當(dāng)前項的寬度。
在示例中,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
}
}
}
}

當(dāng)與 ListView 結(jié)合使用高亮?xí)r,可以使用許多屬性來控制其行為。 highlightRangeMode 控制高亮顯示在視圖中顯示的內(nèi)容。默認(rèn)設(shè)置 ListView.NoHighlightRange 意味著視圖中的項目的高亮和可見范圍根本不相關(guān)。
ListView.StrictlyEnforceRange 的值確保高亮顯示始終可見。如果操作嘗試將突出顯示區(qū)域移動到視圖的可見部分之外,則當(dāng)前項目將相應(yīng)更改,以使突出顯示保持可見。
比較中性的值是 ListView.ApplyRange。它試圖保持高亮顯示可見,但不會更改當(dāng)前項以執(zhí)行此操作。 相反,如果需要,高亮顯示可以移出視圖的可視范圍。
在默認(rèn)配置中,視圖負責(zé)將高亮移動到當(dāng)前元素的位置??梢砸运俣然虺掷m(xù)時間來控制移動和調(diào)整大小的速度。涉及的屬性是 highlightMoveSpeed,highlightMoveDuration,highlightResizeSpeed 和 highlightResizeDuration。 默認(rèn)情況下,速度設(shè)置為每秒400像素,持續(xù)時間設(shè)置為-1,表示速度和距離控制持續(xù)時間。 如果同時設(shè)置了速度和持續(xù)時間,則產(chǎn)生最快動畫的設(shè)置生效。
要更加精確地控制高亮的移動,可以將 highlightFollowCurrentItem 屬性設(shè)置為 false。這意味著視圖不再對高亮代理的移動負責(zé)。此時,高亮代理的運動可以通過我們的自定義的 Behavior 或動畫來進行控制。
在下面的示例中,高亮代理的 y 屬性綁定到 ListView.view.currentItem.y 附加屬性。 這樣可確保突出顯示符合當(dāng)前項目。然而,由于我們不讓視圖本身來移動高亮,我們可以控制高亮元素的移動方式。這是通 Behavior on y 的方法完成的。在下面的例子中,移動分為三個步驟:淡出,移動,淡入。請注意 SequentialAnimation 元素如何與 PropertyAnimation 和 NumberAnimation 組合使用,以創(chuàng)建更復(fù)雜的運動動畫效果。
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 內(nèi)容的最后,可以介紹一下 header 和 footer 屬性。這些可以被認(rèn)為是列表開頭或末尾的特殊代理位置。對于水平列表,這些不會出現(xiàn)在頭部或尾部,而是在開始或結(jié)束時顯示,具體取決于所使用的 layoutDirection 屬性的值。
下面的示例說明了如何使用頁眉和頁腳來增強對列表的開始和結(jié)束的感知。這些特殊列表元素還有其他用途。例如,它們可以用于安放一個用于加載更多的內(nèi)容的按鈕。
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
}
}
}
** 注意: **
頁眉和頁腳代理不會實現(xiàn) ListView 的 spacing 屬性,而是緊挨著顯示在列表中的代理。這意味著如果有必要我們必須自己來實現(xiàn)頁眉和頁腳項的 spacing 的效果。

6.3.4 網(wǎng)格視圖
使用 GridView 非常類似于使用 ListView。唯一的區(qū)別是網(wǎng)格視圖將代理放置在二維網(wǎng)格而不是線性列表中。

與列表視圖相比,網(wǎng)格視圖不依賴于間距和其代理的大小。相反,它使用 cellWidth 和 cellHeight 屬性來控制代理內(nèi)容的大小。然后,默認(rèn)情況下每個代理項目都放在每個這樣的單元格的左上角。
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 屬性控制網(wǎng)格視圖內(nèi)的元素的方向。它可以設(shè)置為 GridView.LeftToRight 或 GridView.TopToBottom。 前一個值從左到右填充網(wǎng)格,從頂部到底部添加行。 視圖可在垂直方向上滾動。 后一個值從頂部到底部添加項目,從左到右填充視圖。 在這種情況下,滾動方向是水平的。
除了 flow 屬性之外,layoutDirection 屬性可以根據(jù)所使用的值,將網(wǎng)格的方向調(diào)整為從左到右或從右到左的視圖。