QML BOOK 第六章 Model-View-Delegate

6. Model-View-Delegate

注意
这章的源代码可以在http://qmlbook.org/assets/中找到。


数组

在QtQuick中,数据经过model-view(模型-视图)分离。对于每一个view(视图),每一个数据元素的可视化都分给一个代理(delegate)。QtQuick附带了一组预约义的模型与视图。想要使用这个系统,必须理解这些类,而且知道如何建立合适的代理来得到正确的显示和交互。性能优化

6.1 概念(Concept)

对于开发用户界面,最重要的一方面是保持数据与可视化的分离。例如,一个电话薄可使用一个垂直文本链表排列或者使用一个网格联系人图片排列。在这两个案例中,数据都是相同的,可是可视化效果倒是不一样的。这种方法一般被称做model-view(模型-视图)模式。在这种模式中,数据一般被称做model(模型),可视化处理称做view(视图)。
在QML中,model(模型)与view(视图)都经过delegate(代理)加入。功能划分以下,model(模型)提供数据。对于每一个数据项,可能有多个值。在下面的例子中,每一个电话薄条目都有一个名字,一个图片和一个号码。数据在view(视图)中被排列,每项使用一个delegate(代理)来实现可视化。view(视图)的任务是排列这些delegate(代理),每一个delegate(代理)将model item(模型项)的值显示给用户。网络

                                                                    


6.2 基础模型(Basic Model)

最基本的分离数据与显示的方法是使用Repeater元素。它被用于实例化一组元素项,而且很容易与一个用于填充用户界面的定位器相结合。
最基本的实现举例,repeater元素用于实现子元素的标号。每一个子元素都拥有一个能够访问的属性index,用于区分不一样的子元素。在下面的例子中,一个repeater元素建立了10个子项,子项的数量由model属性控制。对于每一个子项Rectangle包含了一个Text元素,你能够将text属性设置为index的值,所以能够看到子项的编号是0~9。并发

这是一个不错的编号列表,有时咱们想显示一些更复杂的数据。使用一个JavaScript序列来替换整形变量model的值能够达到咱们的目的。序列可使用任何类型的内容,能够是字符串,整数,或者对象。在下面的例子中,使用了一个字符串链表。咱们仍然使用index的值做为变量,而且咱们也访问modelData中包含的每一个元素的数据。app


将数据暴露成一组序列,你能够经过标号迅速的找到你须要的信息。想象一下这个模型的草图,这是一个最简单的模型,也是一般都会使用的模型,ListModel(链表模型)。一个链表模型由许多ListElement(链表元素)组成。在每一个链表元素中,能够绑定值到属性上。例如在下面这个例子中,每一个元素都提供了一个名字和一个颜色。
每一个元素中的属性绑定链接到repeater实例化的子项上。这意味着变量name和surfaceColor能够被repeater建立的每一个Rectangle和Text项引用。这不只能够方便的访问数据,也可使源代码更加容易阅读。surfaceColor是名字左边园的颜色,而不是模糊的数据序列列i或者行j。异步

repeater的内容的每一个子项实例化时绑定了默认的属性delegate(代理)。这意味着例1(第一个代码段)的代码与下面显示的代码是相同的。注意,惟一的不一样是delegate属性名,将会在后面详细讲解。函数

6.3 动态视图(Dynamic Views)

Repeater元素适合有限的静态数据,可是在真正使用时,模型一般更加复杂和庞大,咱们须要一个更加智能的解决方案。QtQuick提供了ListView和GridView元素,这两个都是基于Flickable(可滑动)区域的元素,所以用户能够放入更大的数据。同时,它们限制了同时实例化的代理数量。对于一个大型的模型,这意味着在同一个场景下只会加载有限的元素。性能

这两个元素的用法很是相似,咱们由ListView开始,而后会描述GridView的模型起点来进行比较。
ListView与Repeater元素像素,它使用了一个model,使用delegate来实例化,而且在两个delegate之间可以设置间隔sapcing。下面的列表显示了怎样设置一个简单的链表。学习

若是模型包含的数据比屏幕上显示的更多,ListView元素只会显示部分的链表内容。而后因为QtQuick的默认行为致使的问题,列表视图不会限制被显示的代理项(delegates)只在限制区域内显示。这意味着代理项能够在列表视图外显示,用户能够看见在列表视图外动态的建立和销毁这些代理项(delegates)。为了防止这个问题,ListView经过设置clip属性为true,来激活裁剪功能。下面的图片展现了这个结果,左边是clip属性设置为false的对比。优化

对于用户,ListView(列表视图)是一个滚动区域。它支持惯性滚动,这意味着它能够快速的翻阅内容。默认模式下,它能够在内容最后继续伸展,而后反弹回去,这个信号告诉用户已经到达内容的末尾。
视图末尾的行为是由到boundsBehavior属性的控制的。这是一个枚举值,而且能够配置为默认的Flickable.DragAndOvershootBounds,视图能够经过它的边界线来拖拽和翻阅,配置为Flickable.StopAtBounds,视图将再也不能够移动到它的边界线以外。配置为Flickable.DragOverBounds,用户能够将视图拖拽到它的边界线外,可是在边界线上翻阅将无效。
使用snapMode属性能够限制一个视图内元素的中止位置。默认行为下是ListView.NoSnap,容许视图内元素在任何位置中止。将snapMode属性设置为ListView.SnapToItem,视图顶部将会与元素对象的顶部对齐排列。使用ListView.SnapOneItem,当鼠标或者触摸释放时,视图将会中止在第一个可见的元素,这种模式对于浏览页面很是便利。

6.3.1 方向(Orientation)

默认的链表视图只提供了一个垂直方向的滚动条,可是水平滚动条也是须要的。链表视图的方向由属性orientation控制。它可以被设置为默认值ListView.Vertical或者ListView.Horizontal。下面是一个水平链表视图。

按照上面的设置,水平链表视图默认的元素顺序方向是由左到右。能够经过设置layoutDirection属性来控制元素顺序方向,它能够设置为Qt.LeftToRight或者Qt.RightToLeft。

6.3.2 键盘导航和高亮

当使用基于触摸方式的链表视图时,默认提供的视图已经足够使用。在使用键盘甚至仅仅经过方向键选择一个元素的场景下,须要有标识当前选中元素的机制。在QML中,这被叫作高亮。
视图支持设置一个当前视图中显示代理元素中的高亮代理。它是一个附加的代理元素,这个元素仅仅只实例化一次,并移动到与当前元素相同的位置。
在下面例子的演示中,有两个属性来完成这个工做。首先是focus属性设置为true,它设置链表视图可以得到键盘焦点。而后是highlight属性,指出使用的高亮代理元素。高亮代理元素的x,y与height属性由当前元素指定。若是宽度没有特别指定,当前元素的宽度也能够用于高亮代理元素。
在例子中,ListView.view.width属性被绑定用于高亮元素的宽度。关于代理元素的使绑定属性将在后面的章节讨论,可是最好知道相同的绑定属性也能够用于高亮代理元素。


当使用高亮与链表视图(ListView)结合时,一些属性能够用来控制它的行为。highlightRangeMode控制了高亮如何影响视图中当前的显示。默认设置ListView.NoHighLighRange意味着高亮与视图中的元素距离不相关。
ListView.StrictlyEnforceRnage确保了高亮始终可见,若是某个动做尝试将高亮移出当前视图可见范围,当前元素将会自动切换,确保了高亮始终可见。
ListView.ApplyRange,它尝试保持高亮代理始终可见,可是不会强制切换当前元素始终可见。若是在须要的状况下高亮代理容许被移出当前视图。
在默认配置下,视图负责高亮移动到指定位置,移动的速度与大小的改变可以被控制,使用一个速度值或者一个动做持续时间来完成它。这些属性包括highlightMoveSpeed,highlightMoveDuration,highlightResizeSpeed和highlightResizeDuration。默认下速度被设置为每秒400像素,动做持续时间为-1,代表速度和距离控制了动做的持续时间。若是速度与动做持续时间都被设置,动画将会采用速度较快的结果来完成。
为了更加详细的控制高亮的移动,highlightFollowCurrentItem属性设置为false。这意味着视图将再也不负责高亮代理的移动。取而代之能够经过一个行为(Bahavior)或者一个动画来控制它。
在下面的例子中,高亮道理的y坐标属性与ListView.view.currentItem.y属性绑定。这确保了高亮始终跟随当前元素。然而,因为咱们没有让视图来移动这个高亮代理,咱们须要控制这个元素如何移动,经过Behavior on y来完成这个操做,在下面的例子中,移动分为三步完成:淡出,移动,淡入。注意怎样使用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: 100 }
                    PropertyAnimation { target: highlightRectangle; property: "opacity"; to: 1; duration: 200 }
                } 
            }
            
            Rectangle {
                id: highlightRectangle 
                anchors.fill: parent
                color: "lightGreen"
            }
        }
    }


6.3.3 页眉与页脚(Header and Footer)

这一节是链表视图最后的内容,咱们可以向链表视图中插入一个页眉(header)元素和一个页脚(footer)元素。这部分是链表的开始或者结尾处被做为代理元素特殊的区域。对于一个水平链表视图,不会存在页眉或者页脚,可是也有开始和结尾处,这取决于layoutDirection的设置。
下面这个例子展现了如何使用一个页眉和页脚来突出链表的开始与结尾。这些特殊的链表元素也有其它的做用,例如,它们可以保持链表中的按键加载更多的内容。

import QtQuick 2.0

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

注意
页眉与页脚代理元素不遵循链表视图(ListView)的间隔(spacing)属性,它们被直接放在相邻的链表元素之上或之下。这意味着页眉与页脚的间隔必须经过页眉与页脚元素本身设置。


6.3.4 网格视图(The GridView)

使用网格视图(GridView)与使用链表视图(ListView)的方式很是相似。真正不一样的地方是网格视图(GridView)使用了一个二维数组来存放元素,而链表视图(ListView)是使用的线性链表来存放元素。

与链表视图(ListView)比较,网格视图(GridView)不依赖于元素间隔和大小来配置元素。它使用单元宽度(cellWidth)与单元高度(cellHeight)属性来控制数组内的二维元素的内容。每一个元素从左上角开始依次放入单元格。

import QtQuick 2.0

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

一个网格视图(GridView)也包含了页脚与页眉,也可使用高亮代理而且支持捕捉模式(snap mode)的多种反弹行为。它也可使用不一样的方向(orientations)与定向(directions)来定位。
定向使用flow属性来控制。它能够被设置为GridView.LeftToRight或者GridView.TopToBottom。模型的值从左往右向网格中填充,行添加是从上往下。视图使用一个垂直方向的滚动条。后面添加的元素也是由上到下,由左到右。
附加还有flow属性和layoutDirection属性,可以适配网格从左到右或者从右到左,这依赖于你使用的设置值。

6.4 代理(Delegate)

当使用模型与视图来自定义用户界面时,代理在建立显示时扮演了大量的角色。在模型中的每一个元素经过代理来实现可视化,用户真实可见的是这些代理元素。
每一个代理访问到索引号或者绑定的属性,一些是来自数据模型,一些来自视图。来自模型的数据将会经过属性传递到代理。来自视图的数据将会经过属性传递视图中与代理相关的状态信息。
一般使用的视图绑定属性是ListView.isCurrentItem和ListView.view。第一个是一个布尔值,标识这个元素是不是视图当前元素,这个值是只读的,引用自当前视图。经过访问视图,能够建立可复用的代理,这些代理在被包含时会自动匹配视图的大小。在下面这个例子中,每一个代理的width(宽度)属性与视图的width(宽度)属性绑定,每一个代理的背景颜色color依赖于绑定的属性ListView.isCurrentItem属性。

import QtQuick 2.0

Rectangle {
    width: 120
    height: 300
    
    color: "white"

    ListView {
        anchors.fill: parent
        anchors.margins: 20
        
        clip: true
        
        model: 100
        
        delegate: numberDelegate
        spacing: 5
        
        focus: true
    }
    
    Component {
        id: numberDelegate
     
        Rectangle {
            width: ListView.view.width
            height: 40
            
            color: ListView.isCurrentItem?"gray":"lightGray"
            
            Text {
                anchors.centerIn: parent
                
                font.pixelSize: 10
                
                text: index
            }
        }
    }
}


若是在模型中的每一个元素与一个动做相关,例如点击做用于一个元素时,这个功能是代理完成的。这是由事件管理分配给视图的,这个操做控制了视图中元素的导航,代理控制了特定元素上的动做。
最基础的方法是在每一个代理中建立一个MouseArea(鼠标区域)而且响应onClicked信号。在后面章节中将会演示这个例子。

6.4.1 动画添加与移除元素(Animating Added and Removed Items)

在某些状况下,视图中的显示内容会随着时间而改变。因为模型数据的改变,元素会添加或者移除。在这些状况下,一个比较好的作法是使用可视化队列给用户一个方向的感受来帮助用户知道哪些数据被加入或者移除。
为了方便使用,QML视图为每一个代理绑定了两个信号,onAdd和onRemove。使用动画链接它们,能够方便建立识别哪些内容被添加或删除的动画。
下面这个例子演示了如何动态填充一个链表模型(ListModel)。在屏幕下方,有一个添加新元素的按钮。当点击它时,会调用模型的append方法来添加一个新的元素。这个操做会触发视图建立一个新的代理,并发送GridView.onAdd信号。SequentialAnimation队列动画与这个信号链接绑定,使用代理的scale属性来放大视图元素。
当视图中的一个代理点击时,将会调用模型的remove方法将一个元素从模型中移除。这个操做将会致使GridView.onRemove信号的发送,触发另外一个SequentialAnimation。这时,代理的销毁将会延迟直到动画完成。为了完成这个操做,PropertyAction元素须要在动画前设置GridView.delayRemove属性为true,并在动画后设置为false。这样确保了动画在代理项移除前完成。

import QtQuick 2.0
Rectangle {
    width: 480
    height: 300
    
    color: "white"

    ListModel {
        id: theModel

        ListElement { number: 0 }
        ListElement { number: 1 }
        ListElement { number: 2 }
        ListElement { number: 3 }
        ListElement { number: 4 }
        ListElement { number: 5 }
        ListElement { number: 6 }
        ListElement { number: 7 }
        ListElement { number: 8 }
        ListElement { number: 9 }
    }
    
    Rectangle {
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.margins: 20
        
        height: 40
        
        color: "darkGreen"
        
        Text {
            anchors.centerIn: parent
            
            text: "Add item!"
        }
        
        MouseArea {
            anchors.fill: parent
            
            onClicked: {
                theModel.append({"number": ++parent.count});
            }
        }
        
        property int count: 9
    }
    
    GridView {
        anchors.fill: parent
        anchors.margins: 20
        anchors.bottomMargin: 80
        
        clip: true
        
        model: theModel
        
        cellWidth: 45
        cellHeight: 45

        delegate: numberDelegate
    }
    
    Component {
        id: numberDelegate

        Rectangle {
            id: wrapper
            
            width: 40
            height: 40
            
            color: "lightGreen"
            
            Text {
                anchors.centerIn: parent
                
                font.pixelSize: 10
                
                text: number
            }
            
            MouseArea {
                anchors.fill: parent
                
                onClicked: {
                    if (!wrapper.GridView.delayRemove)
                        theModel.remove(index);
                }
            }
            
            GridView.onRemove: SequentialAnimation {
                PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: true }
                NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing.type: Easing.InOutQuad }
                PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: false }
            }

            GridView.onAdd: SequentialAnimation {
                NumberAnimation { target: wrapper; property: "scale"; from: 0; to: 1; duration: 250; easing.type: Easing.InOutQuad }
            }
        }
    }
}


6.4.2 形变的代理(Shape-Shifting Delegates)

在使用链表时一般会使用当前项激活时展开的机制。这个操做能够被用于动态的将当前项目填充到整个屏幕来添加一个新的用户界面,或者为链表中的当前项提供更多的信息。
在下面的例子中,当点击链表项时,链表项都会展开填充整个链表视图(ListView)。额外的间隔区域被用于添加更多的信息,这种机制使用一个状态来控制,当一个链表项展开时,代理项都能输入expanded(展开)状态,在这种状态下一些属性被改变。
首先,包装器(wrapper)的高度(height)被设置为链表视图(ListView)的高度。标签图片被放大而且下移,使图片从小图片的位置移向大图片的位置。除了这些以外,两个隐藏项,实际视图(factsView)与关闭按键(closeButton)切换它的opactiy(透明度)显示出来。最后设置链表视图(ListView)。
设置链表视图(ListView)包含了设置内容Y坐标(contentsY),这是视图顶部可见的部分代理的Y轴坐标。另外一个变化是设置视图的交互(interactive)为false。这个操做阻止了视图的移动,用户再也不可以经过滚动条切换当前项。
因为设置第一个链表项为可点击,向它输入一个expanded(展开)状态,致使了它的代理项被填充到整个链表而且内容重置。当点击关闭按钮时,清空状态,致使它的代理项返回上一个状态,而且从新设置链表视图(ListView)有效。

import QtQuick 2.0

Item {
    width: 300
    height: 480
    
    ListView {
        id: listView
        
        anchors.fill: parent
        
        delegate: detailsDelegate
        model: planets
    }
    
    ListModel {
        id: planets
        
        ListElement { name: "Mercury"; imageSource: "images/mercury.jpeg"; facts: "Mercury is the smallest planet in the Solar System. It is the closest planet to the sun. It makes one trip around the Sun once every 87.969 days." }
        ListElement { name: "Venus"; imageSource: "images/venus.jpeg"; facts: "Venus is the second planet from the Sun. It is a terrestrial planet because it has a solid, rocky surface. The other terrestrial planets are Mercury, Earth and Mars. Astronomers have known Venus for thousands of years." }
        ListElement { name: "Earth"; imageSource: "images/earth.jpeg"; facts: "The Earth is the third planet from the Sun. It is one of the four terrestrial planets in our Solar System. This means most of its mass is solid. The other three are Mercury, Venus and Mars. The Earth is also called the Blue Planet, 'Planet Earth', and 'Terra'." }
        ListElement { name: "Mars"; imageSource: "images/mars.jpeg"; facts: "Mars is the fourth planet from the Sun in the Solar System. Mars is dry, rocky and cold. It is home to the largest volcano in the Solar System. Mars is named after the mythological Roman god of war because it is a red planet, which signifies the colour of blood." }
    }
    
    Component {
        id: detailsDelegate
        
        Item {
            id: wrapper
            
            width: listView.width
            height: 30
            
            Rectangle {
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top
                
                height: 30
                
                color: "#ffaa00"
                
                Text {
                    anchors.left: parent.left
                    anchors.verticalCenter: parent.verticalCenter
                    
                    font.pixelSize: parent.height-4
                 
                    text: name
                }
            }
            
            Rectangle {
                id: image
                
                color: "black"
                
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.rightMargin: 2
                anchors.topMargin: 2
                
                width: 26
                height: 26
                
                Image {
                    anchors.fill: parent
                    
                    fillMode: Image.PreserveAspectFit
                    
                    source: imageSource
                }
            }
            
            MouseArea {
                anchors.fill: parent
                onClicked: parent.state = "expanded"
            }
            
            Item {
                id: factsView
                
                anchors.top: image.bottom
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: parent.bottom
                
                opacity: 0
                
                Rectangle {
                    anchors.fill: parent
                    
                    color: "#cccccc"
                    
                    Text {
                        anchors.fill: parent
                        anchors.margins: 5
                        
                        clip: true
                        wrapMode: Text.WordWrap
                        
                        font.pixelSize: 12
                        
                        text: facts
                    }   
                }
            }
            
            Rectangle {
                id: closeButton
                
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.rightMargin: 2
                anchors.topMargin: 2
                
                width: 26
                height: 26
                
                color: "red"
                
                opacity: 0

                MouseArea {
                    anchors.fill: parent
                    onClicked: wrapper.state = ""
                }
            }
            
            states: [ 
                State {
                    name: "expanded"
                    
                    PropertyChanges { target: wrapper; height: listView.height }
                    PropertyChanges { target: image; width: listView.width; height: listView.width; anchors.rightMargin: 0; anchors.topMargin: 30 }
                    PropertyChanges { target: factsView; opacity: 1 }
                    PropertyChanges { target: closeButton; opacity: 1 }
                    PropertyChanges { target: wrapper.ListView.view; contentY: wrapper.y; interactive: false }
                } 
            ]
            
            transitions: [
                Transition {
                    NumberAnimation {
                        duration: 200;
                        properties: "height,width,anchors.rightMargin,anchors.topMargin,opacity,contentY"
                    }
                }
            ]
        }
    }
}


这个技术展现了展开代理来填充视图可以简单的经过代理的形变来完成。例如当浏览一个歌曲的链表时,能够经过放大当前项来对该项添加更多的说明。

6.5 更高级的用法(Advanced Techniques)

6.5.1 路径视图(The PathView)

路径视图(PathView)很是强大,但也很是复杂,这个视图由QtQuick提供。它建立了一个可让子项沿着任意路径移动的视图。沿着相同的路径,使用缩放(scale),透明(opacity)等元素能够更加详细的控制过程。
当使用路径视图(PathView)时,你必须定义一个代理和一个路径。在这些之上,路径视图(PathView)自己也能够自定义一些属性的区间。一般会使用pathItemCount属性,它控制了一次可见的子项总数。preferredHighLightBegin属性控制了高亮区间,preferredHighlightEnd与highlightRangeMode,控制了当前项怎样沿着路径显示。
在关注高亮区间以前,咱们必须先看看路径(path)这个属性。路径(path)属性使用一个路径(path)元素来定义路径视图(PathView)内代理的滚动路径。路径使用startx与starty属性来连接路径(path)元素,例如PathLine,PathQuad和PathCubic。这些元素都使用二维数组来构造路径。
当路径定义好以后,可使用PathPercent和PathAttribute元素来进一步设置。它们被放置在路径元素之间,而且为通过它们的路径和代理提供更加细致的控制。PathPercent提供了如何控制每一个元素之间覆盖区域部分的路径,而后反过来控制分布在这条路径上的代理元素,它们被按比例的分布播放。
preferredHightlightBegin与preferredHighlightEnd属性由PathView(路径视图)输入到图片元素中。它们的值在0~1之间。结束值大于等于开始值。例如设置这些属性值为0.5,当前项只会显示当前百分之50的图像在这个路径上。
在Path中,PathAttribute元素也是被放置在元素之间的,就像PathPercent元素。它们可让你指定属性的值而后插入的路径中去。这些属性与代理绑定能够用来控制任意的属性。

下面这个例子展现了路径视图(PathView)如何建立一个卡片视图,而且用户能够滑动它。咱们使用了一些技巧来完成这个例子。路径由PathLine元素组成。使用PathPercent元素,它确保了中间的元素居中,而且给其它的元素提供了足够的空间。使用PathAttribute元素来控制旋转,大小和深度值(z-value)。

在这个路径之上(path),须要设置路径视图(PathView)的pathItemCount属性。它控制了路径的浓密度。路径视图的路径(PathView.onPath)使用preferredHighlightBegin与preferredHighlightEnd来控制可见的代理项。

代理以下面所示,使用了一些从PathAttribute中连接的属性,itemZ,itemAngle和itemScale。须要注意代理连接的属性只在wrapper中可用。所以,rotxs属性在Rotation元素中定义为可访问值。
另外一个须要注意的是路径视图(PathView)连接的PathView.onPath属性的用法。一般对于这个属性都绑定为可见,这样容许路径视图(PathView)缓冲不可见的元素。这不是经过剪裁处理来实现的,由于路径视图(PathView)的代理比其它的视图,例如链表视图(ListView)或者栅格视图(GridView)放置更加随意。

当在路径视图(PathView)上使用图像转换或者其它更加复杂的元素时,有一个性能优化的技巧是绑定图像元素(Image)的smooth属性与PathView.view.moving属性。这意味着图像在移动时可能不够完美,可是可以比较平滑的转换。当视图在移动时,对于平滑缩放的处理是没有意义的,由于用户根本看不见这个过程。

6.5.2 XML模型(A Model from XML)

因为XML是一种常见的数据格式,QML提供了XmlListModel元素来包装XML数据。这个元素可以获取本地或者网络上的XML数据,而后经过XPath解析这些数据。
下面这个例子展现了从RSS流中获取图片,源属性(source)引用了一个网络地址,这个数据会自动下载。




当数据下载完成后,它会被加工做为模型的子项。查询属性(query)是一个XPath代理的基础查询,用来建立模型项。在这个例子中,这个路径是/rss/channel/item,因此,在一个模型子项建立后,每个子项的标签,都包含了一个频道标签,包含一个RSS标签。
每个模型项,一些规则须要被提取,由XmlRole元素来代理。每个规则都须要一个名称,这样代理才可以经过属性绑定来访问。每一个这样的属性的值都经过XPath查询来肯定。例如标题属性(title)符合title/string()查询,返回内容中在之间的值。
图像源属性(imageSource)更加有趣,由于它不只仅是从XML中提取字符串,也须要加载它。在流数据的支持下,每一个子项包含了一个图片。使用XPath的函数substring-after与substring-before,能够提取本地的图片资源。这样imageSource属性就能够直接被做为一个Image元素的source属性使用。

6.5.3 链表分段(Lists with Sections)

有时,链表的数据须要划分段。例如使用首字母来划分联系人,或者音乐。使用链表视图能够把平面列表按类别划分。


为了使用分段,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)使用每一个国家做为标题。在每一个段中,spacemen模型中的名字使用spaceManDelegate组件来代理显示。



6.5.4 性能协调(Tunning Performance)

一个模型视图的性能很大程度上依赖于代理的建立。例如滚动下拉一个链表视图时,代理从外部加入到视图底部,而且从视图顶部移出。若是设置剪裁(clip)属性为false,而且代理项花了不少时间来初始化,用户会感受到视图滚动体验不好。
为了优化这个问题,你能够在滚动时使用像素来调整。使用cacheBuffer属性,在上诉状况下的垂直滚动,它将会调整在链表视图的上下须要预先准备好多少像素的代理项,结合异步加载图像元素(Image),例如在它们进入视图以前加载。
建立更多的代理项将会牺牲一些流畅的体验,而且花更多的时间来初始化每一个代理。这并不表明能够解决一些更加复杂的代理项的问题。在每次实例化代理时,它的内容都会被评估和编辑。这须要花费时间,若是它花费了太多的时间,它将会致使一个不好的滚动体验。在一个代理中包含太多的元素也会下降滚动的性能。
为了补救这个问题,咱们推荐使用动态加载元素。当它们须要时,能够初始化这些附加的元素。例如,一个展开代理可能推迟它的详细内容的实例化,直到须要使用它时。每一个代理中最好减小JavaScript的数量。将每一个代理中复杂的JavaScript调用放在外面来实现。这将会减小每一个代理在建立时编译JavaScript。

6.6 总结(Summary)

在这个章节中,咱们学习了模型,视图与代理。每一个数据的入口是模型,视图经过可视化代理来实现数据的可视化。将数据从显示中分离出来。一个模型能够是一个整数,提供给代理使用的索引值(index )。若是JavaScript数组被做为一个模型,模型数据变量(modelData)表明了数组的数据的当前索引。对于更加复杂的状况,每一个数据项须要提供多个值,使用链表模型(ListModel)与链表元素(ListElement)是一个更好的解决办法。对于静态模型,一个Repeater能够被用做视图。它能够很是方便的使用行(Row),列(Column),栅格(Grid),或者流(Flow)来建立用户界面。对于动态或者大的数据模型,使用ListView或者GridView更加适合。它们会在须要时动态的建立代理,减小在场景下一次显示的元素的数量。在视图中的代理能够与数据模型中的属性静态绑定,或者动态绑定。使用视图的onAdd与onRemove信号,能够动态播放的它们的显示与消失。

相关文章
相关标签/搜索