# 动态视图

重复器适用于有限的静态数据集,但在现实世界中,模型通常更复杂——数据量也更大。 在这里,需要一个更智能的解决方案。 为此,Qt Quick提供了ListViewGridView元素,它们都基于Flickable(可滑动)区域,因此用户可以在更大的数据集中移动。 同时,它们限制了并发实例化代理的数量。 对于大型模型,这意味着一次场景中的元素更少。

image

image

这两种元素的用法相似。 首先分析ListView,然后以与其对比的方式描述GridView。 请注意,GridView将项目列表放置到二维网格中,可以从左到右或从上到下。 如果要显示数据表,则需要使用“表模型”章节中描述的TableView”。

ListViewRepeater类似,它使用model实例化delegate(代理),并且在代理之间可以有spacing(空隙)。 下面显示了怎样简单设置外观:

import QtQuick
import "../common"

Background {
    width: 80
    height: 300

    ListView {
        anchors.fill: parent
        anchors.margins: 20
        clip: true
        model: 100
        delegate: GreenBox {
            required property int index
            width: 40
            height: 40
            text: index
        }
        spacing: 5
    }
}

image

如果模型包含的数据超出屏幕所能容纳的范围,则ListView仅显示列表的一部分。 但是,由于Qt Quick的默认行为,列表视图不会限制显示代理的屏幕区域。 这意味着,代理可能在列表视图之外的区域可见,并且在列表视图之外动态创建和销毁代理对用户是可见的。 为了防止这种情况,必须通过在ListView元素上将clip属性设置为true来激活剪切。 下图显示了与将clip属性为false时(右视图)与true时(左视图)的对比。

image

对于用户来说,ListView是一个可滚动的区域。它支持动态滚动,这意味着可以滑动它以快速移动内容。默认情况下,它可以被拖拽到显示超出本身内容末尾(最后的列表元素)的内容(拉伸出的部分空白),然后回弹,向用户发出已到达结尾的信号。

视图末尾的行为使用boundsBehavior属性控制。这是一个枚举值,默认行为是Flickable.DragAndOvershootBounds,视图可以拖动到超出边界,并有回弹效果;Flickable.StopAtBounds,视图永远不会超出其边界;Flickable.DragOverBounds, 允许用户将视图拖动到超出边界,但回弹在其边界处停止。

可以限制允许视图停止的位置,这使用snapMode属性控制。ListView.NoSnap,默认行为,可以让视图停在任何位置;ListView.SnapToItem,内部项目的顶部将始终与视图的顶部对齐。ListView.SnapOneItem,当释放鼠标按钮或触摸时,视图将最多移动不超过一个项目。在翻页时,最后一种模式非常方便。

# 方向

列表视图默认是垂直滚动,但同样可以水平滚动。 列表视图的方向通过orientation属性控制,它可以设置为ListView.Vertical(默认值)、ListView.Horizontal。 水平列表视图如下所示:

import QtQuick
import "../common"

Background {
    width: 480
    height: 80

    ListView {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 4
        clip: true
        model: 100
        orientation: ListView.Horizontal
        delegate: GreenBox {
            required property int index
            width: 40
            height: 40
            text: index
        }
    }
}

image

如痛所见的,默认情况下,在水平方向元素从左向右排列。 这可以通过layoutDirection属性控制,根据不同需要的流向,该属性可以设置为Qt.LeftToRightQt.RightToLeft

# 按键导航与高亮

在基于触摸的设置中使用ListView,视图本身就能表达足够的含义。在一个有键盘,甚至用方向键选择一个项时,需要一种机制来指示当前的项。在QML中,将这种机制称作高亮显示。

视图支持高亮代理,它与那些代理一起显示在视图中。它可以被认为是一个额外的代理,只是它只被实例化一次,并且移动到与当前项相同的位置。

将在下面示例中演示,这涉及到两个属性。首先,将focus属性设置为true,这给了ListView键盘焦点;其次,highlight属性设置成要使用的高亮代理。高亮代理被赋予当前项的xyheight;如果未指定width,则还使用当前项的宽度。

在示例中,附加属性ListView.view.width用于宽度。可用于代理的附加属性将在本章的有关代理部分的章节进一步讨论,但了解相同的属性也可用于高亮显示代理也很好。

import QtQuick
import "../common"

Background {
    width: 240
    height: 300

    ListView {
        id: view

        anchors.fill: parent
        anchors.margins: 20

        focus: true

        model: 100
        delegate: numberDelegate
        highlight: highlightComponent
        
        spacing: 5
        clip: true
    }

    Component {
        id: highlightComponent

        GreenBox {
            width: ListView.view ? ListView.view.width : 0
        }
    }

    Component {
        id: numberDelegate

        Item {
            id: wrapper

            required property int index

            width: ListView.view ? ListView.view.width : 0
            height: 40

            Text {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: wrapper.index
            }
        }
    }
}

image

ListView与高亮显示结合使用时,可以使用许多属性来控制其行为。 highlightRangeMode控制视图中显示的内容如何影响高亮显示。ListView.NoHighlightRange,是默认设置,表示视图中可见范围与项目的高亮显示一点也不相关。 译者笔记:在这种模式下,如果是最下面超出可见范围,使用上下方向的方向按钮控制当前项的选择,新的当前项会显示在最下方;如果是从最上面超出显示范围,新选中的项将会出现在最上方;如果在可视范围内切换当前项,总体列表项的位置不上下移动,只有当前选中项的效果上下移动。

ListView.StrictlyEnforceRange确保高亮显示的项始终可见。如果一个动作试图将高亮部分移动出视图的可见部分,当前项也将相应改变,以便高亮项始终保持可见。译者笔记:在这种模式下,当前项会呈现在可视范围的最上面,即使是最后一项,也会呈现在可视区域中的第一项,其下面的区域将会空白。

上面两个的折中的值是ListView.ApplyRange,它尝试保持高亮的项可见,但不会更改当前项目来强制实现其可见性; 相反,如有必要,允许将高亮显示项移出视图。译者笔记:在这种模式下,当前项会呈现在可视范围的最上面;但是在列表中最后一项出现在可见区域的下方时,高亮项将会能向下移动,最后课到达可视范围的最下方。

在默认配置中,视图负责将高亮区域移动到对应位置。移动和调整尺寸的速度可以被速度或持续时间控制,涉及的属性是highlightMoveSpeedhighlightMoveDurationhighlightResizeSpeedhighlightResizeDuration。默认情况下,速度设置为每秒400个像素,持续时间设置为-1,表示使用速度和距离控制持续时间。如果同时设置了速度和持续时间,则会选择生成最快动画的那个。

想要更细地控制高亮区域的移动,可以将highlightFollowCurrentItem属性设置为false。这意味着视图不再负责高亮代理的移动;相反,可以通过Behavior或动画来控制移动。

在下面的示例中,高亮代理的y属性绑定到附加属性ListView.view.currentItem.y上,这确保高亮显示跟随当前项。然而,由于不让视图移动区域,可以控制元素的移动方式,这通过Behavior on y完成。在下面的示例中,运动分为三个步骤:淡出(fading out)、移动(moving)、淡入(fading in)。请注意如何将SequentialAnimationPropertyAnimation元素与NumberAnimation结合使用以创建更复杂的运动。

Component {
    id: highlightComponent

    Item {
        width: ListView.view ? ListView.view.width : 0
        height: ListView.view ? ListView.view.currentItem.height : 0

        y: ListView.view ? ListView.view.currentItem.y : 0

        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
        }
    }
}

# 页眉和页脚

ListView内容的两端,可以插入一个header和一个footer元素,它们可以被视为位于列表开头或结尾的特殊代理。 对于水平列表,它们不会出现在头部或脚部,而是出现在开头或结尾,具体出现位置取决于所使用的layoutDirection

下面的示例说明了如何使用页眉和页脚来增强列表开头和结尾的观感。 这些特殊列表元素还有其他用途,例如,它们可用于放置按钮用来加载更多的内容。

import QtQuick
import "../common"

Background {
    width: 240
    height: 300

    ListView {
        anchors.fill: parent
        anchors.margins: 20

        clip: true

        model: 4
        delegate: numberDelegate
        header: headerComponent
        footer: footerComponent

        spacing: 2
    }

    Component {
        id: headerComponent

        YellowBox {
            width: ListView.view ? ListView.view.width : 0
            height: 20
            text: 'Header'

        }
    }

    Component {
        id: footerComponent

        YellowBox {
            width: ListView.view ? ListView.view.width : 0
            height: 20
            text: 'Footer'
        }
    }

    Component {
        id: numberDelegate

        GreenBox {
            required property int index
            
            width: ListView.view.width
            height: 40
            
            text: 'Item #' + index
        }
    }
}

image

提示

页眉和页脚代理不遵守ListViewspacing(间距)属性,而是将它们直接放置在列表中的挨着的代理的旁边。 这意味着它的任何间距都必须是页眉和页脚项的一部分。

# 网格视图(GridView)

GridView(网格视图)的使用与ListView(列表视图)非常相似。 唯一真正的区别是网格视图将代理放在二维网格中,而不是线性列表中。

image

与列表视图相比,网格视图不依赖于它的代理的间距和大小。 相反,它使用cellWidthcellHeight属性来控制内容代理的尺寸。 然后将每个代理项放置在每个此类单元格的左上角。

import QtQuick
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: GreenBox {
            required property int index
            width: 40
            height: 40
            text: index
        }
    }
}

GridView包含页眉和页脚、可以使用高亮代理、并支持对齐模式、以及各种边界行为。 它也可以在不同的方向和方向上定向。它也可以在控制布局元素方向(directions)和内容元素的填充方向(orientations)。

使用flow属性控制内容的填充方向。 它可以设置为GridView.LeftToRightGridView.TopToBottomGridView.LeftToRight是从左到右填充网格,从上到下添加行,视图可在垂直方向滚动。 GridView.TopToBottom从上到下添加项,从左到右填充视图,在这种情况下,滚动方向是水平的。

除了flow属性,layoutDirection属性可以根据使用的值将网格的方向适配从左到右(left-to-right)或从右到左(right-to-left)的语言方向。

最后更新: 12/14/2021, 11:58:48 PM