QML Book 第六章 模型、视图及代理 4

6.5 视图知识进阶

6.5.1 PathView

PathView 元素是 Qt Quick 中最强大的,也是最复杂的视图。它可以创建一个视图,其中项目沿任意路径布置。沿着相同的路径,可以详细地控制尺度,不透明度等更多的属性。

使用 PathView 时,必须定义一个代理和一个路径。 除此之外,PathView 本身可以通过一系列属性进行自定义。 最常见的是 pathItemCount,一次控制可见项目的数量,高亮范围控制属性 preferredHighlightBegin,preferredHighlightEnd 和 highlightRangeMode,控制沿着当前项目的路径显示的位置。

在深入了解高亮范围控制属性之前,我们必须先看一下 path 属性。 path 属性希望在 PathView 正在滚动时定义代理遵循的路径的 Path 元素。该路径使用与路径元素(如 PathLine,PathQuad 和 PathCubic)组合的 startX 和 startY 属性来定义。 这些元素连接在一起以形成二维路径。

当定义了路径时,可以使用 PathPercent 和 PathAttribute 元素进一步调整路径。这些放置在路径元素之间,并为路径和代理提供了更精细的粒度控制。PathPercent 控制在每个元素之间覆盖的路径的一部分的大小。反过来,这样做可以控制沿途的代理分布,因为它们按比例分配到进度的百分比。

PathView 的 preferredHighlightBegin 和 preferredHighlightEnd 属性是进入视图的位置属性。他们都期望在 0 和 1 之间的范围内的小数值。预期结束位置也将或多或少等于预期的起始位置。将这两个属性设置为例如 0.5,当前项目将显示在该路径的百分之五十的位置。

在 Path 中,PathAttribute 元素放置在元素之间,就像 PathPercent 元素一样。它们允许您指定沿路径插值的属性值。这些属性附加到代理,并可用于控制任何可想到的属性。

pathview-coverview

下面的示例演示了如何使用 PathView 元素创建用户可以翻转的卡片的视图。它采取了一些技巧来做到这一点。路径由三个 PathLine 元素组成。使用 PathPercent 元素,中心元素正好居中,并提供足够的空间,不被其他元素遮挡。使用 PathAttribute 元素,可以控制旋转,大小和 z 值等属性。

除路径之外,PathView 的 pathItemCount 属性已设置。这样可以控制路径上的可视元素密度。 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 元素内访问该值。

PathView 值得注意的另一个细节是使用附加的 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
        }
    }

当在 PathView 中转换图像或其他复杂元素时,常用的性能优化技巧是将 Image 元素的平滑(smooth)属性绑定到附加属性 PathView.view.moving。 这意味着图像在移动时不那么平滑漂亮,但在静止时使图像平滑。 当视图处于运动状态时,无需花费处理能力来使图像平滑,因为用户将无法看到这一点。

6.5.2 从 XML 获取模型数据

由于 XML 是无处不在的数据格式,QML 提供了将 XML 数据作为模型的 XmlListModel 元素。该元素可以从本地或远程获取 XML 数据,然后使用 XPath 表达式处理数据。

下面的例子演示从 RSS 流中获取图像。源属性是指通过 HTTP 的删除位置,数据自动下载。

xmllistmodel-images

当数据被下载时,它被处理成模型项目和角色。query 属性是表示创建模型项目的基本查询的 XPath。在此示例中,路径为 /rss/channel/item,因此对于 RSS 标签内的通道标签内的每个项目标签,都会创建一个模型项目。

对于每个模型项目,都会提取多个角色。这些由 XmlRole 元素表示。给每个角色一个名称,代表可以通过附加的属性访问。通过每个角色的 XPath 查询来确定每个这样的属性的实际值。例如,title 属性对应于 title/string() 查询,返回 <title> 和 </ title> 标签之间的内容。

imageSource 属性更有趣,因为它不仅从 XML 中提取字符串,还可以处理它。在提供的流中,每个项目都包含一个由 <img src= 标签表示的图像。使用 XPath 的函数 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 列表的章节功能

有时,列表中的数据可以分为几个部分。可以将联系人列表分成类似专辑下的字母表或音乐曲目的每个字母下的部分的形式。使用 ListView 可以将普通的列表分成几个类别,从而为查阅提供更多的深度。

listview-sections

为了使用 section,必须设置 section.property 和 section.criteria。section.property 定义了哪些属性用于将内容分成几个部分。这里,需要注意的是要知道必须对模型进行排序,以便每个部分由连续元素组成,否则相同的属性名称可能会显示在多个位置。

section.criteria 可以设置为 ViewSection.FullString 或 ViewSection.FirstCharacter。第一个是默认值,可用于具有清晰部分的模型,例如音乐专辑的曲目。后者采用属性的第一个字符,意味着可以使用任何属性。最常见的例子是电话簿中联系人的姓氏。

当定义了这些部分时,可以使用附加的属性 ListView.section,ListView.previousSection 和 ListView.nextSection 从每个项目访问它们。使用这些属性,可以检测一个部分的第一个和最后一个项目,并相应地进行操作。

也可以将部分代理组件分配给 ListView 的 section.delegate 属性。这将创建一个标题代理,它插入到节的任何项目之前。代理组件可以使用附加属性 section 访问当前标题的名称。

下面的例子通过显示在他们的国籍之后划分的空间男子列表来演示该节的概念。nation 被用作 section.property。section.delegate 组件 sectionDelegate 显示每个国家的标题,显示国家的名称。在每个章节中,使用 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 调整性能

模型视图的可感知的性能差别在很大程度上取决于准备新代理所需的时间。例如,当通过 ListView 向下滚动时,代理将添加到底部的视图下面,并且在视图顶部视图中被移除。如果 clip 属性设置为 false,这个过程将变得更加明显。如果代理花费太多的时间进行初始化,一旦视图滚动得太快,用户就会感知到性能上的卡顿。

要解决此问题,我们可以调整滚动视图四周的边距(以像素为单位)。这是使用 cacheBuffer 属性完成的。在上述情况下,垂直滚动,它将控制将包含准备代理的 ListView 上下几个像素。例如,将它与异步加载的图像元素相结合可以在图像被加入视图之前给予图像加载时间。

让更多的代理牺牲内存以获得更平滑的体验,同时会消耗更多的时间来初始化每个代理。这不能解决复杂代理的问题。每次代理被实例化,它的内容被解析和编译。这需要时间,如果在解析和编译时花费太多时间,将导致滚动的体验不佳。在代理中加入了太多的元素也会降低滚动性能,因为这将导致花费更多的时间去移动更多的元素。

要解决后面的两个问题,建议使用 Loader 元素。这些可以用于在需要时再去实例化的其他元素。例如,扩展代理可以使用 Loader 来推迟其真正视图的实例化,直到需要时再加载真正的视图。由于同样的原因,每个代理中将 JavaScript 脚本的调用数量保持在最小值是很好的。最好让他们调用驻留在每个代理之外的元素(一般是相应的视图)中定义的 JavaScript 脚本。这样就减少了每次创建代理时解析和编译 JavaScript 脚本所花费的时间。

6.6 总结

在本章中,我们研究了模型,视图和代理。对于模型中的每个数据条目,视图实例化可视化数据的代理。这实现了数据与视图的分离。

模型可以是单个整数,其中索引变量提供给代理。如果使用 JavaScript 数组作为模型,则 modelData 变量表示数组当前索引的数据,而 index 仍然会保留用作索引。对于更复杂的情况,每个数据项需要提供多个值,则使用 ListElement 项填充的 ListModel 是一个更好的解决方案。

对于静态模型,可以使用 Repeater 作为视图。将其与定位器,如行(Row)、列(Column)、网格(Grid )或流(Flow),组合很容易,以构建用户界面部件。对于动态或大型数据模型,ListView 或 GridView 等视图更合适。它们根据需要在运行中创建代理实例,可以减少场景中的实际显示的元素数量。

视图中使用的代理可以是具有绑定到模型数据的属性的静态项,或者它们可以是动态的,状态取决于它们是否在焦点。使用该视图的附加 onAdd 和 onRemove 信号,可以在代理内容显示和消失添加动画效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容