动画(Animation)和过度(Transition)元素
Transition - 状态改变的过度动画
SequentialAnimation - 串行执行动画
ParallelAnimation - 并行执行动画
Behavior - 为属性变化指定默认动画
PropertyAction - 动画中设置立即改变的属性值(Sets immediate property changes during animation)
PauseAnimation - 在动画中引入暂停
SmoothedAnimation - 运行属性平滑的过度到一个新的值
SpringAnimation - 以弹跳式的方式给属性设置一个新值
ScriptAction - 动画中执行脚本
动画属性元素的数据类型
PropertyAnimation - 属性改变动画
NumberAnimation - qreal类型属性改变动画
Vector3dAnimation - QVector3d类型属性改变动画
ColorAnimation - 颜色改变动画
RotationAnimation -旋转动画
ParentAnimation - parent更改动画
AnchorAnimation - 描点改变动画
在QML中,向属性值应用animation元素来创建动画.Animation元素通过篡改属性的值来产生平滑的过度.同样,也可使用状态过度(state transition)向状态变化设置一个动画.
要创建动画,需要使用与要创建动画的属性类型相匹配的animation元素,应用的动画(animation)与需要的行为类型有关.
触发动画
有很多种方式在对象上设置动画.
直接的属性动画Direct Property Animation
要实现立即移动或动画移动,可直接设置属性值.这可以在信号处理函数或附加属性中实现..
Rectangle {
id: blob
width: 75; height: 75
color: "blue"
MouseArea {
anchors.fill: parent
onClicked: blob.color = "green"
}
}
然而,为产生更多控制,属性动画(property animation)使用在属性变化值之间进行插值实现了平滑移动.属性动画提供了定时控制和由easing curves指定的不同插值曲线.
Rectangle {
id: flashingblob
width: 75; height: 75
color: "blue"
opacity: 1.0
MouseArea {
anchors.fill: parent
onClicked: {
animateColor.start()
animateOpacity.start()
}
}
PropertyAnimation {id: animateColor; target: flashingblob; properties: "color"; to: "green"; duration: 100}
NumberAnimation {
id: animateOpacity
target: flashingblob
properties: "opacity"
from: 0.99
to: 1.0
loops: Animation.Infinite
easing {type: Easing.OutBack; overshoot: 500}
}
}
特定的属性动画元素(property animation elements)与PropertyAnimation元素的实现相比效率更好.他们向不同的QML类型如int,color,rotation设置动画,PropertyAnimation可以设置parent改变动画.
不同动画属性的更多信息详见Controlling Animations小节.
状态变化的过度
States是属性的配置集合,针对不同的状态设置不同的属性值.状态变化直接导致其中的属性变化;动画平滑过度可产生生动的状态变化效果.
Transition元素自动实现了动画元素(animation elements)的功能,为状态变化引起的属性变化产生插值.要设置一个对象的过度(transition),可构造其transitions属性.
按钮可能有两个状态,用户点击时为pressed状态,用户释放鼠标后为released状态.我们可以为每个状态设置不同的属性配置.过度(transition)可以为状态从pressed改变为released产生动画.同样也可为状态从released改变为pressed产生动画.
Rectangle {
width: 75; height: 75
id: button
state: "RELEASED"
MouseArea {
anchors.fill: parent
onPressed: button.state = "PRESSED"
onReleased: button.state = "RELEASED"
}
states: [
State {
name: "PRESSED"
PropertyChanges { target: button; color: "lightblue"}
},
State {
name: "RELEASED"
PropertyChanges { target: button; color: "lightsteelblue"}
}
]
transitions: [
Transition {
from: "PRESSED"
to: "RELEASED"
ColorAnimation { target: button; duration: 100}
},
Transition {
from: "RELEASED"
to: "PRESSED"
ColorAnimation { target: button; duration: 100}
}
]
}
to和from属性绑定到特定的状态名称上,可以设置过度(transition)针对的属性变化.为简化或使过度对称,可设置to属性为通配符星号("*"),指示这个过度可用于所有的状态改变.
transitions:
Transition {
to: "*"
ColorAnimation { target: button; duration: 100}
}
默认的行为动画(Default Animation as Behaviors)
默认的属性动画使用行为动画(behavior animations)来设置.使用指定属性的Behavior元素声明的动画,使属性值变化时产生动画.然而,Behavior元素有一个enabled属性可设置行为动画的使能.
下面是一个球(ball)组件,可对其x,y,color属性设置行为动画(behavior animation).行为动画被设置为模拟弹性效果.当球移动时就会在属性值变化上应用这个弹性效果.
Rectangle {
width: 75; height: 75; radius: width
id: ball
color: "salmon"
Behavior on x {
NumberAnimation {
id: bouncebehavior
easing {
type: Easing.OutElastic
amplitude: 1.0
period: 0.5
}
}
}
Behavior on y {
animation: bouncebehavior
}
Behavior {
ColorAnimation { target: ball; duration: 100 }
}
}
有很多种方式给属性设置行为动画.Behavior on <property>声明是给属性设置行为动画的便利方式.
行为属性的演示内容见Behaviors example.
串行或并行执行动画
动画可并行或串行运行.并行动画可同时执行一组动画,而串行动画按顺序执行一组动画:一个执行完毕后在执行另一个.在SequentialAnimation 和ParallelAnimation中分组的动画将串行或并行执行.
下面的banner组件需要一个挨一个的显示多个图标或广告.opacity属性变为1.0来表示一个不透明对象.使用SequentialAnimation元素,前一个动画执行完毕后才会执行opacity动画.ParallelAnimation元素将同时执行动画.
Rectangle {
id: banner
width: 150; height: 100; border.color: "black"
Column {
anchors.centerIn: parent
Text {
id: code
text: "Code less."
opacity: 0.01
}
Text {
id: create
text: "Create more."
opacity: 0.01
}
Text {
id: deploy
text: "Deploy everywhere."
opacity: 0.01
}
}
MouseArea {
anchors.fill: parent
onPressed: playbanner.start()
}
SequentialAnimation {
id: playbanner
running: false
NumberAnimation { target: code; property: "opacity"; to: 1.0; duration: 200}
NumberAnimation { target: create; property: "opacity"; to: 1.0; duration: 200}
NumberAnimation { target: deploy; property: "opacity"; to: 1.0; duration: 200}
}
}
一旦有动画被放在了SequentialAnimation 或 ParallelAnimation中, 他们就不会再独立的启动或停止.串行或并行动画必须作为一个组合来启动或停止.
SequentialAnimation元素在执行过度动画(transition animations)时也很有效,因为在过度中的动画是并行执行的.
有关在QML中创建和组合多个动画的演示请见Animation basics example.
控制动画
有不同的方式控制动画.
动画回放
所有的动画元素都是从Animation元素继承的;这些元素为动画元素提供了必要的属性和方法.动画元素具有start(),stop(),resume(),pause()和complete()方法--这些方法控制了动画的执行.
Easing
Easing曲线定义动画如何在起始值和终止值见产生插值.不同的easing曲线定义了一系列的插值.easing曲线简化了创建动画的效果--如弹跳效果, 加速, 减速, 和周期动画.
QML对象中可能对每个属性动画都设置了不同的easing曲线.同时也有不同的参数用于控制曲线,有些是特除曲线独有的.更多信息见easing 文档.
easing example 可视化的演示了不同easing曲线类型效果.
其他动画元素
此外,QML提供了几个对动画有用的其他元素:
PauseAnimation: 允许停止动画
ScriptAction: 允许在动画期间执行JavaScript,可与StateChangeScript配合来重用已存在的脚本.
PropertyAction: 在动画期间直接修改属性,而不会产生属性变化动画
下面是针对不同属性类型的特殊动画元素
SmoothedAnimation: 特殊的NumberAnimation,当目标值发生改变时提供平滑的动画效果
SpringAnimation: 指定mass, damping 和epsilon等特殊属性来提供一个弹簧效果的动画
ParentAnimation: 用在parent变化时的动画(见ParentChange)
AnchorAnimation: 用在描点变化时的动画(见AnchorChanges)
使用QML视图显示数据
视图是包含项目的集合.他们富有特色,可自定义风格和行为
Qt Quick图形元素提供了几个标准的视图:
ListView 水平或垂直列表中排列项目
GridView 在一个有效空间的网格内排列项目
PathView 在路径上排列项目
WebView - 可在QtWebKit QML Module中使用.
与其他视图不同,WebView 不具有全部视图特性,需要与Flickable组合创建一个像Web浏览器一样执行的项目.
这些元素具有的属性和行为相互独立.更多信息见他们的文档.
模型
视图在屏幕上显示模型(models).模型可以是简单的整形列表或一个C++模型.
要给视图设置模型,需要给视图的model属性绑定到一个模型.
ListModel {
id: petlist
ListElement { type: "Cat" }
ListElement { type: "Dog" }
ListElement { type: "Mouse" }
ListElement { type: "Rabbit" }
ListElement { type: "Horse" }
}
ListView {
id: view
anchors.fill: parent
model: petlist
delegate: petdelegate
}
更多信息见QML Data Models 文档.
视图代理
视图需要使用代理(delegate)来可视化的表现列表中的项.视图以代理作为模版显示列表中的每个项.模型中的项使用index属性来访问.
Component {
id: petdelegate
Text {
id: label
font.pixelSize: 24
text: if (index == 0)
label.text = type + " (default)"
else
text: type
}
}
美化视图
可使用decoration属性来自定义视图的header,footer,section属性.通过向这些属性绑定其他可视对象,就可美化视图.footer中可能包含一个Rectangle元素作为边框,或在header中显示列表的logo图标.
假如一个俱乐部要使用它们的商标颜色来修饰其成员列表.成员列表包含在一个模型中,代理显示模型中的内容.
ListModel {
id: nameModel
ListElement { name: "Alice" }
ListElement { name: "Bob" }
ListElement { name: "Jane" }
ListElement { name: "Harry" }
ListElement { name: "Wendy" }
}
Component {
id: nameDelegate
Text {
text: name;
font.pixelSize: 24
}
}
可以向header和footer属性绑定可视对象来美化这个俱乐部的成员列表.这个可视对象可以直接定义,或在其他文件中定义,或在组件元素中定义..
ListView {
anchors.fill: parent
clip: true
model: nameModel
delegate: nameDelegate
header: bannercomponent
footer: Rectangle {
width: parent.width; height: 30;
gradient: clubcolors
}
highlight: Rectangle {
width: parent.width
color: "lightgray"
}
}
Component { //instantiated when header is processed
id: bannercomponent
Rectangle {
id: banner
width: parent.width; height: 50
gradient: clubcolors
border {color: "#9EDDF2"; width: 2}
Text {
anchors.centerIn: parent
text: "Club Members"
font.pixelSize: 32
}
}
}
Gradient {
id: clubcolors
GradientStop { position: 0.0; color: "#8EE2FE"}
GradientStop { position: 0.66; color: "#7ED2EE"}
}
ListView的小节
ListView 可在sections中包含很多分组,相关的列表项按他们所在的小节进行标记.而且小节还可以指定代理(delegates).
如下列表中包含了人员姓名和所在小组的信息.
ListModel {
id: nameModel
ListElement { name: "Alice"; team: "Crypto" }
ListElement { name: "Bob"; team: "Crypto" }
ListElement { name: "Jane"; team: "QA" }
ListElement { name: "Victor"; team: "QA" }
ListElement { name: "Wendy"; team: "Graphics" }
}
Component {
id: nameDelegate
Text {
text: name;
font.pixelSize: 24
anchors.left: parent.left
anchors.leftMargin: 2
}
}
ListView 元素具有一个叫做section的附加属性(attached property) 可在一个小节内组合临近或相关的元素.section的property属性指明列表元素中的一个属性作为小节名称.criteria属性指明如何显示小节的名称,而delegate与视图的delegate相同.
ListView {
anchors.fill: parent
model: nameModel
delegate: nameDelegate
focus: true
highlight: Rectangle {
color: "lightblue"
width: parent.width
}
section {
property: "team"
criteria: ViewSection.FullString
delegate: Rectangle {
color: "#b0dfb0"
width: parent.width
height: childrenRect.height + 4
Text { anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 16
font.bold: true
text: section
}
}
}
}
QML数据模型(Model)
QML数据模型(Model)
QML中的ListView,GridView和Repeater等元素需要数据模型来提供要显示的数据.这些元素需要一个为模型中的每一项数据生成一个实例的代理组件(delegate component).模型可以是静态的,也可对其动态修改,插入,删除,移动.
给代理提供的数据通过叫做角色的数据绑定到代理.下面的ListModel有两个角色,type和age,ListView带有一个代理,并绑定这些角色以显示他们的值:
import QtQuick 1.0
Item {
width: 200; height: 250
ListModel {
id: myModel
ListElement { type: "Dog"; age: 8 }
ListElement { type: "Cat"; age: 5 }
}
Component {
id: myDelegate
Text { text: type + ", " + age }
}
ListView {
anchors.fill: parent
model: myModel
delegate: myDelegate
}
}
如果模型属性和代理的属性有名称冲突,可以在角色名称前加上模型名称.例如,如果Text元素也有type或age属性,上面的text元素会显示这些属性值,而不是模型中的项目.这时,可以使用model.age和model.type,确保代理显示模型中的数据项.
模型中的项目索引角色也可用在代理中.数据项从模型中删除后这个索引值被设置为-1.如果绑定这个索引角色,需要注意这个逻辑上可能的索引值-1,表示这个数据项还不可用.(通常这个数据项很快就会被删除,也可能通过在某些视图设置delayRemove扩展属性,由延时代理销毁.)
对于没有角色名称的数据模型(如下面的QStringList)可以使用modelData角色来引用数据.只有一个角色的模型也可使用modelData角色.这时modelData角色包含的数据与命名的角色相同.
QML提供了几个内建的数据模型元素.此外,可在C++中创建模型,用于QML元素.
视图引用模型中的数据并显示.QML使用Positioner和Repeater项来定位排列模型中的数据项.
QML数据模型ListModel
ListModel是QML中简单的层次元素.其中的角色由ListElement属性指定.
ListModel {
id: fruitModel
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
上面的模型有两个角色--name和cost.他们可以绑定到ListView的代理,如:
ListView {
anchors.fill: parent
model: fruitModel
delegate: Row {
Text { text: "Fruit: " + name }
Text { text: "Cost: $" + cost }
}
}
ListModel提供了可直接在JavaScript中调用的方法.这时,第一次插入的项决定使用模型的视图可用的角色.例如,如果创建了一个空ListModel,并在JavaScript中填充,第一次插入时指定的角色将显示在视图中:
ListModel { id: fruitModel }
...
MouseArea {
anchors.fill: parent
onClicked: fruitModel.append({"cost": 5.95, "name":"Pizza"})
}
当点击MouseArea时,fruitModel中会产生两个角色,cost和name.后面增加其他角色,使用模型的视图也只能使用这两个.调用ListModel::clear()重置模型中的角色.
XmlListModel
XmlListModel用来从XML数据源构造模型.角色由XmlRole元素指定.
下面的模型有三个角色:title,link和description:
XmlListModel {
id: feedModel
source: "http://rss.news.yahoo.com/rss/oceania"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
XmlRole { name: "link"; query: "link/string()" }
XmlRole { name: "description"; query: "description/string()" }
}
RSS范例展示了如何使用XmlListModel显示RSS源.
VisualItemModel
VisualItemModel允许QML元素作为模型.
这个模型中包含数据和代理;VisualItemModel的子项提供了代理的内容.这个模型不提供任何角色.
VisualItemModel {
id: itemModel
Rectangle { height: 30; width: 80; color: "red" }
Rectangle { height: 30; width: 80; color: "green" }
Rectangle { height: 30; width: 80; color: "blue" }
}
ListView {
anchors.fill: parent
model: itemModel
}
注意上例中不需要代理.模型中的数据项提供了可视化元素,将显示到视图中..
C++数据模型
C++中定义的模型可用于QML.可向QML元素暴露C++中已存的数据模型或复杂的数据集.
C++中可使用QStringList, QList<QObject*> 或QAbstractItemModel类定义模型.前面两个可用于暴露简单的数据集,QAbstractItemModel则可定义更复杂的模型.
基于QStringList的模型
可定义简单的QStringList模型,使用modalData角色访问其中的内容.
下面的ListView中有一个代理使用modelData角色使用模型中的数据项:
ListView {
width: 100; height: 100
anchors.fill: parent
model: myModel
delegate: Rectangle {
height: 25
width: 100
Text { text: modelData }
}
}
Qt应用程序可以加载这个QML文档并设置一个叫做myModel的QStringList类型的值:
QStringList dataList;
dataList.append("Item 1");
dataList.append("Item 2");
dataList.append("Item 3");
dataList.append("Item 4");
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));
完整范例见Qt的范例:examples/declarative/modelviews/stringlistmodel目录.
注意:视图无法知道QStringList中的内容变化.如果QStringList发生变化,需要再次调用QDeclarativeContext::setContextProperty()重置模型.
基于QObjectList的模型
QObject*列表也可作为模型.QList<QObject*>提供了一个对象列表的属性.
下面的应用程序创建了一个DataObject对象,使用Q_PROPERTY声明的命名属性,当QList<DataObject>暴露给QML时,可按属性名称作为角色来访问:
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
...
};
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QList<QObject*> dataList;
dataList.append(new DataObject("Item 1", "red"));
dataList.append(new DataObject("Item 2", "green"));
dataList.append(new DataObject("Item 3", "blue"));
dataList.append(new DataObject("Item 4", "yellow"));
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));
...
QObject*可作为modelData的属性来访问.为方便,对象的属性也可直接用于代理上下文.这里,view.qml在ListView代理中引用了数据模型属性:
ListView {
width: 100; height: 100
anchors.fill: parent
model: myModel
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color
Text { text: name }
}
}
注意使用color属性时使用了全名称标识.QML对象的属性不能与模型对象属性同名,可使用modelData对象来访问数据项.
完整范例见examples/declarative/modelviews/objectlistmodel.
注意:无法知道QList中的内容发生了变化.如果QList变化了,有必要调用QDeclarativeContext::setContextProperty()重置模型.
QAbstractItemModel
可使用QAbstractItemModel的子类定义模型.如果有一个复杂的模型无法用其他方式实现,就可以使用这种方式.QAbstractItemModel在发生变化时会自动通知QML视图.
QAbstractItemModel子类向QML暴露的角色可使用QAbstractItemModel::setRoleNames()设置.默认的角色名称由Qt设置:
Qt Role QML角色名称
Qt::DisplayRole 显示
Qt::DecorationRole 修饰
下面的应用程序有一个QAbstractItemModel子类模型叫做AnialModel,具有type和size角色.为在QML中访问这些属性调用了QAbstractItemModel::setRoleNames():
class Animal
{
public:
Animal(const QString &type, const QString &size);
...
};
class AnimalModel : public QAbstractListModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
...
};
AnimalModel::AnimalModel(QObject *parent)
: QAbstractListModel(parent)
{
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
setRoleNames(roles);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal("Wolf", "Medium"));
model.addAnimal(Animal("Polar bear", "Large"));
model.addAnimal(Animal("Quoll", "Small"));
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", &model);
...
这个模型显示在ListView中,代理可访问type和size角色:
ListView {
width: 200; height: 250
anchors.fill: parent
model: myModel
delegate: Text { text: "Animal: " + type + ", " + size }
}
模型更改后QML视图自动更新.记住模型更改必须遵守标准规则,当模型修改后必须调用QAbstractItemModel::dataChanged(),QAbstractItemModel::beginInsertRows()等等,通知视图模型被修改了.更多信息见模型子类的参考.
完整范例见examples/declarative/modelviews/abstractitemmodel 目录.
QAbstractItemModel表现为一个层次表,但现在QML只提供了可显示列表数据的视图.为了显示模型层次的子列表,VisualDataModel元素提供了几个属性和方法与QAbstractItemModel类型的模型共用:
hasModelChildren 确定节点是否有子节点.
VisualDataModel::rootIndex 指定跟节点
VisualDataModel::modelIndex() 返回可给VisualDataModel::rootIndex赋值的QModelIndex类型值
VisualDataModel::parentModelIndex() 返回可给VisualDataModel::rootIndex赋值的QModelIndex类型值
向QML暴露C++数据模型
上例中使用QDeclarativeContext::setContextProperty()设置可用于QML组件的模型.另一种方式是在C++插件中注册一个C++模型类作为QML类型.这样可以直接在QML中声明模型元素:
class MyModelPlugin : public QDeclarativeExtensionPlugin
{
public:
void registerTypes(const char *uri)
{
qmlRegisterType<MyModel>(uri, 1, 0,
"MyModel");
}
}
Q_EXPORT_PLUGIN2(mymodelplugin, MyModelPlugin);
MyModel {
id: myModel
ListElement { someProperty: "some value" }
}
ListView {
width: 200; height: 250
model: myModel
delegate: Text { text: someProperty }
}
写C++插件的更多信息见Writing QML extensions with C++.
其他数据模型--整形
一个整数可指示模型包含的元素个数.这时,模型没有任何数据角色.
下面范例中创建一个有五个元素的ListView:
Item {
width: 200; height: 250
Component {
id: itemDelegate
Text { text: "I am item number: " + index }
}
ListView {
anchors.fill: parent
model: 5
delegate: itemDelegate
}
}
对象实例
一个对象实例可指定为模型.对象属性可作为角色.
下例创建一个有一个项的列表,展示Text.text的颜色.注意这里使用了全命名model.color避免命名冲突.
Rectangle {
width: 200; height: 250
Text {
id: myText
text: "Hello"
color: "#dd44ee"
}
Component {
id: myDelegate
Text { text: model.color }
}
ListView {
anchors.fill: parent
anchors.topMargin: 30
model: myText
delegate: myDelegate
}
}
在代理中访问视图和模型
可在代理中访问其所在的视图,在ListView的代理中使用ListView.view属性,或GridView中使用GridVie.view等.而且可使用ListView.view.model来访问模型的属性.
这对于在多个视图时使用同一个代理很有用,例如,想为每个视图创建不同的修饰部分或其他特性,需要针对每个视图设置不同的属性.同样,可能需要访问或显示模型的属性.
下面例子中,代理显示模型的language属性,一个字段的颜色依赖于fruit_color属性.
Rectangle {
width: 200; height: 200
ListModel {
id: fruitModel
property string language: "en"
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
Component {
id: fruitDelegate
Row {
Text { text: " Fruit: " + name; color: ListView.view.fruit_color }
Text { text: " Cost: $" + cost }
Text { text: " Language: " + ListView.view.model.language }
}
}
ListView {
property color fruit_color: "green"
model: fruitModel
delegate: fruitDelegate
anchors.fill: parent
}
}
其他重要的情形是当有些情况下(如鼠标点击)代理需要更新模型中的数据.这时可以在模型中定义一个函数,如:
setData(int row, const QString & field_name, QVariant new_value),
...在代理中调用这个函数:
ListView.view.model.setData(index, field, value)
...假设field保存了需要更新的字段名称,value中保存了新值.
在QML中管理动态对象
QML提供了很多种方式动态创建和管理QML对象.Loader,Repeater,ListView,GridView和PathView都支持动态对象管理.对象也可在C++中创建并管理,这是QML\C++相结合的应用程序首选方法.
QML也支持在Javascript代码中动态创建对象.这在当前QML文件无法满足需要是很有用,而且不会卷入额外的C++组件.
本文中下面的动态场景范例展示了这个概念.
动态创建对象
在JavaScript中动态创建对象有两种方式.可调用Qt.createComponent()动态创建一个组件对象,或使用Qt.createQmlObject()从一个描述QML的字符串创建元素.最好是有一个定义在.qml文件中的组件,需要动态创建这个组件的实例.否则,就需要在运行时使用一个QML描述字符串创建元素.
动态创建组件
要动态加载QML文件中的组件,可在QML全局对象中调用Qt.createComponent()函数.这个函数只有一个指向QML文件的URL参数并创建这个URL指向的组件对象.
创建了组件后,可以调用其createObject()方法创建组件的实例.这个函数有一个或两个参数:
第一个参数是新元素的parent.由于图形项(graphical item)在场景中必须有parent,否则就无法显示,还是推荐使用这种方式设置一个parent.然而,如果希望稍后在设置可以传递一个null实参.
第二个参数是可选的属性名称-值的映射列表,用来初始化元素的属性值.这个参数指定的属性值在对象构造完成之前被赋给对象的属性,避免了有些属性在用于绑定前必须被初始化而发生的错误.在属性用于绑定前就已经设置了值.另外,与对象创建后再设置属性值和绑定相比,具有稍微的效率优势.
下面是一个范例,首先定义Sprite.qml文件:
import QtQuick 1.0
Rectangle { width: 80; height: 50; color: "red" }
主应用程序文件是main.qml,导入可创建Spite对象的componentCreation.js JavaScript文件:
import QtQuick 1.0
import "componentCreation.js" as MyScript
Rectangle {
id: appWindow
width: 300; height: 300
Component.onCompleted: MyScript.createSpriteObjects();
}
下面是componentCreation.js. 注意在调用createObject()前先判断组件状态是否为Component.Ready,如果QML是从网上加载的,不会立刻完成的,必须等待.
var component;
var sprite;
function createSpriteObjects() {
component = Qt.createComponent("Sprite.qml");
if (component.status == Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
if (component.status == Component.Ready) {
sprite = component.createObject(appWindow, {"x": 100, "y": 100});
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} else if (component.status == Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
}
}
如果从本地文件加载QML文件,可以忽略finishCreation()函数直接调用createObject():
function createSpriteObjects() {
component = Qt.createComponent("Sprite.qml");
sprite = component.createObject(appWindow, {"x": 100, "y": 100});
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
}
注意这两个实例中,createObject()函数的第一个参数是appWindow,使新创建的对象作为main.qml中的appWindow对象的子对象.否则,新创建的对象不会在场景中显示.
当用相对路径引用文件时,路径是相对于执行Qt.CreatComponent()函数的文件而言的.
要连接动态创建对象上的信号(或槽),可使用信号的connect()方法.
基于QML的字符串创建对象
如果在运行时还没有建立QML文件,可以使用Qt.CreateQmlObject()函数使用QML描述字符串来创建QML对象,如下所示:
var newObject = Qt.createQmlObject('import QtQuick 1.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem, "dynamicSnippet1");
第一个参数是创建QML的字符串.与创建新的QML文件一样,需要导入必要的类型.第二个参数是新对象的parent;这个parent必须在场景中存在.第三个参数新对象相关的文件连接,用于导出错误报表.
如果QML中使用相对路径导入文件,路径是相对于定义了parent对象的文件而言的.
维护动态创建的对象
要管理动态创建的对象,必须要确保创建上下文的生命周期比被创建的对象长.否则当创建上下文被释放后,对动态对象的绑定不在生效.
事实上创建上下文依赖于对象是如何创建的:
如果使用Qt.createComponent(),创建上下文是调用这个方法的QDeclarativeContext
如果调用Qt.createQmlObject(),创建上下文是传递给这个函数的parent参数的上下文
如果定义了Component{} 对象并在其上调用了createObject(),创建上下文是定义的那个Component
同时主要动态创建对象可像其他对象一样使用,在QML中没有id属性.
删除动态对象
在很多用户接口中,将对象的opacity设置为0或将其移动到屏幕之外,而不是直接删除对象.如果创建了很多动态对象,删除无用的对象会获得显著的效率提升.
主要不要手动删除由QML元素动态创建的对象(如Loader和Repeater).同时,避免删除不是由你创建的动态对象.
对象可调用destroy()方法删除.这个方法有一个可选参数(默认值为0)指定对象被删除的大概延时(毫秒).
下面是一个范例.application.qml创建了五个SelfDestroyingRect.qml组件.每个实例都会运行一个NumberAnimation动画,动画完成后,在其根对象上调用destroy()方法释放自己:
application.qml
import QtQuick 1.0
Item {
id: container
width: 500; height: 100
Component.onCompleted: {
var component = Qt.createComponent("SelfDestroyingRect.qml");
for (var i=0; i<5; i++) {
var object = component.createObject(container);
object.x = (object.width + 10) * i;
}
}
}
SelfDestroyingRect.qml
import QtQuick 1.0
Rectangle {
id: rect
width: 80; height: 80
color: "red"
NumberAnimation on opacity {
to: 0
duration: 1000
onRunningChanged: {
if (!running) {
console.log("Destroying...")
rect.destroy();
}
}
}
}
也可以在application.qml中调用object.destroy()删除其创建的对象.
注意在一个对象上调用destroy()是安全的.在对象上调用destroy()不会马上释放,而是在脚本阻塞后或下一帧时执行清理(除非设置了非0的延时).
注意如果SelfDestroyRect如下方式创建:
Item {
SelfDestroyingRect {
// ...
}
}
由于只能动态释放动态创建的对象,这时会发生错误.
使用Qt.createQmlObject()创建的对象也同样使用destroy()释放:
var newObject = Qt.createQmlObject('import QtQuick 1.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem, "dynamicSnippet1");
newObject.destroy(1000);