首页 > 代码库 > Qt Quick应用开发介绍 10-12(动态界面, 实践学习, 总结和扩展)

Qt Quick应用开发介绍 10-12(动态界面, 实践学习, 总结和扩展)

Chapter10 UI Dynamics and Dynamic UI 动态界面

前面章节学习了在开发时添加item, 让它们invisible; 该怎么做可以让程序根据不同的数据和用户输入来有不同的显示? 这些变化可能比visibility复杂; 我们怎样才能做到让程序UI的动态变化更appealing吸引人, 甚至成为用户体验的一部分? 

10.1 Using States 使用state

网络连接对于现在的版本中天气相关的部件是必须的; 它让网络数据可视化; 如果你的电脑不在线, 启动clock-n-weather应用的话, 只会看到时钟和空的内容;

这是因为WeatherModelItem没能拿到天气数据; 所以没有model项可以显示; 如果你在笔记本或移动设备上使用这个程序, 这种情况会经常发生; 你的程序需要处理没有网络的情况; 我们可以使用State来处理;

QtQuick里的每个item都有一个state属性, 保存了当前state的名字; 也有一个states属性, 是一个States的list; 这个属性包含了那个item所有已知的states; 列表中每个States有个string名字, 定义了一组property值; 需要的话, 它甚至可以包含script代码, 当那个state变化的时候被执行; item可以被设成一个state, 只要把选中的state的名称分配给state属性; refer to QML States

对于我们的程序, 会添加三种state:

- Offline, 开始阶段的初始化状态; 也应用到没有网络连接或程序处于离线状态;

- Loading, 网络连接可用, 但是WeatherModelItem还在加载天气数据; 这个state在网络连接很慢的时候有用; (e.g. 非宽带的移动设备)

- Live Weather, 更新的天气数据可用而且能够显示;

在Offline和Loading状态, 程序只会显示clock, 以一个较大的尺寸显示在屏幕中间; 当Live Weather状态激活, 程序就会显示天气数据;

由于我们的新states和WeatherModelItem的状态紧密相关, 必须把它们直接绑定; WeatherModelItem没有定义任何真实的state; 我们根据current或forecast模型的status, hijack抓取它的states属性来存储Offline, Loading, Live Weather值;

components/WeatherModelItem.qml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
XmlListModel {
    id: current
    source: root.source
    query: "/xml_api_reply/weather/current_conditions"
 
    XmlRole { name: "condition"; query: "condition/@data/string()" }
    XmlRole { name: "temp_c"; query: "temp_c/@data/string()" }
 
    onStatusChanged: {
        root.modelDataError = false
        if (status == XmlListModel.Error) {
            root.state = "Offline"
            root.statusMessage = "Error occurred: " + errorString()
            root.modelDataError = true
            //console.log("Weather Clock: Error reading current: " + root.statusMessage)
        else if (status == XmlListModel.Ready) {
            // check if the loaded model is not empty, and post a message
            if (get(0) === undefined) {
                root.state = "Offline"
                root.statusMessage = "Invalid location \"" + root.location + "\""
                root.modelDataError = true
            else {
                root.state = "Live Weather"
                root.statusMessage = "Live current weather is available"
            }
            //console.log("Weather Clock: " + root.statusMessage)
        else if (status == XmlListModel.Loading) {
            root.state = "Loading"
            root.statusMessage = "Current weather is loading..."
            //console.log("Weather Clock: " + root.statusMessage)
        else if (status == XmlListModel.Null) {
            root.state = "Loading"
            root.statusMessage = "Current weather is empty..."
            //console.log("Weather Clock: " + root.statusMessage)
        else {
            root.modelDataError = true
            console.log("Weather Clock: unknown XmlListModel status:" + status)
        }
    }
}

确切的states是由主要item: WeatherClock.引入的; 这个item有两个新的子item, 持有所有要根据states以不同样子显示的元素;

-clockScreen, 显示一个较大的clock. 对应main item是Offline或Loading的state;

-weatherScreen, 显示clock和天气预报, 对应Live Weather的state, 基本上和clock-n-weather一样;

最后一步, 把WeatherClock的states和WeatherModelItem的state值绑定到一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
states: [
    State {
        name: "Offline"
        PropertyChanges {target: clockScreen; visible: true}
        PropertyChanges {target: weatherScreen; visible: false}
    },
    State {
        name: "Live Weather"
        PropertyChanges {target: clockScreen; visible: false}
        PropertyChanges {target: weatherScreen; visible: true}
    },
    State {
        name: "Loading"
        PropertyChanges {target: clockScreen; visible: true}
        PropertyChanges {target: weatherScreen; visible: false}
        PropertyChanges {target: busyIndicator; on: true}
    }
]

我们的State定义包含了PropertyChanges, 能改变屏幕的visibility, 可以在Loading状态打开busyIndicator;

Loading的state可能会被多次激活; 如果clock没有显示秒, 整个程序可能表现的好像hang住了一样; 我们需要一个动画态的busy indicator(忙碌指示)来告诉用户程序还在运行; Qt example: RSS News Reader 提供了一个漂亮的例子; 我们可以用它来稍作改动; busyIndicator在Loading state变成可见的, 然后通知用户程序正在后台处理数据;

注意我们使用了新的forceOffline设置, 如果forceOffline设置为true, 程序停留在Offline状态, 不管weatherModelItem.有什么变化;

如果我们现在改变states, 改变立即生效; 在state改变时, 有了transitions过渡效果和animation effect动画效果, 程序会看起来更有吸引力; 


10.2 Adding Animations 添加动画

动画不仅仅对可视化的效果有作用; 它们也可以充当一些基本特性, 而使用by other means其他方式可能会很难达到相同效果(e.g. busy indicator) QtQuick提供了丰富的动画框架, 易于使用; 更多的细节本章节可能无法覆盖, 但可以花些时间来理解怎样使用动画;

通常, 所有的动画会控制元素的一个或多个属性, 修改它的visual外观; 这个修改可以有多种动态效果而且在不同的时间跨度span产生; 可以有多个动画平行或依次地应用到相同或不同的元素上; 你可以根据属性的变化显式地或隐式地运行一个动画; 你也可以永久地分配一个给一个属性分配一个动画, 那样只要属性一变化, 动画就会开始; 虽然Qt有个普通的Animation元素, 但多数时间, 你可能会使用一个由QtQuick提供的预定义的动画元素: QML Animation and Transition Elements

添加animation到程序中很简单; 主要的挑战是去找出哪个animation适合使用, 怎么使用它们来组合出需要的视觉效果;

Animation和Transition密切相关, 其定义了一个元素怎样从一个State 变换到另一个; 大多数情况下, 一个transition包含一个animation;

Qt doc提供了animation和transition的总览, 以及使用细节--QML Animation and Transitions

下面的代码块展示了Offline和Live Weather状态间的两个transition--过渡变化; 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
transitions: [
    Transition {
        from: "Offline"
        to: "Live Weather"
        PropertyAnimation {
            target: weatherScreen
            property: "opacity"
            from: 0
            to: 1
            easing.type: Easing.Linear
            duration: 5000
        }
    },
    Transition {
        from: "Live Weather"
        to: "Offline"
        PropertyAnimation {
            target: clockScreen
            property: "opacity"
            from: 0
            to: 1
            easing.type: Easing.Linear
            duration: 5000
        }
    }
]

state改变会跟着weather数据而切换offline view和full view的visibility; 在上面, 增加了一个animation来改变opacity属性; 它会让screen淡出, 在5秒内消失;

Note 理论上, 一个微小的flickering(闪烁)会在transition开始的时候在屏幕上可见, 因为target元素会先变得全部可见, 就在它的opacity变成0之后, animation一开始的时候, ;

busy indicator的功能完全是基于animation的; 这里几乎没有其他代码的实现:

utils/BusyIndicator.qml

1
2
3
4
5
6
7
8
9
10
11
Image {
    id: root
    property bool on: false
 
    source: "../content/resources/busy.png"
    visible: root.on
 
    NumberAnimation on rotation {
        running: root.on; from: 0; to: 360; loops: Animation.Infinite; duration: 1200
    }
}

BusyIndicator可以这样加载:


1
2
3
4
5
6
BusyIndicator {
    id: busyIndicator
    anchors.horizontalCenter: root.horizontalCenter
    anchors.bottom: statusText.top
    anchors.margins: Style.baseMargin
}

下一节将另一个动画, 在clock和weather上实现可视化效果;



10.3 Supporting the Landscape Mode 支持风景模式

如果程序是在移动设备上运行, 它应该有一个clockScreen层和weatherScreen层用来 tailored(适应)风景显示的方向; 因为clockScreen只包含一个item, 我们不需要对它做许多改动, weatherScreen的改动较大;

有一个有趣的方法可以简化实现, 使用Flow代替Colum; Flow将children动态地按照它自己的size来排列; 需要的话, 它会把children wrap到合适的行列中;

Flow有一个特性很好用; move属性上, 我们可以定义一个Transition, 在children在一个Flow中开始移动的时候应用; 我们使用一个NumberAnimation来应用到children的左边, 选择一个bounce(弹跳)效果, Easing.OutBounce给 easing.type:

1
2
3
4
5
6
7
move: Transition {
    NumberAnimation {
        properties: "x,y"
        duration: 500
        easing.type: Easing.OutBounce
    }
}


10.4 Finalizing the Main Item 完成Main项目

我们需要重做main item, 添加价格新特性; 首先我们从 clock-n-weather/ClockAndWeather.qml 中取出main item 然后添加animations和transitions;

另外, 重做的main item有三个button和一个status text在屏幕底下;

点击exitButton退出应用, 点击root item内部将不再起作用;

toggleStatesButton强制下线状态; 可以隐藏天气预报来使用屏幕空间放置一个更大的clock; 它也防止从Internet传递常规的数据;

configureButton显示configure元素, 它持有并且操作了configuration参数; main item将它们绑定到合适的属性上; 这实现了一种全局的程序状态; 

status text根据states改变而更新; 

完全代码: 略 WeatherClock/WeatherClock.qml 


下一步

确实, 我们最后的程序还可以使用许多特性被增强和扩展; 我们选择了最小子集来覆盖教程里的范围, 下一节讨论一些挑选的增强效果;

---10---


Chapter11 Doing More, Learning More 实践能学到更多

11.1 Porting to Qt5 转到Qt5

Qt5包含新版本的QtQuick: 2.0; 另外, 由于模块化的关系, 有些预设的component中有些改动; 我们需要做两个基本改动:

1) 把import QtQuick 1.x改成import QtQuick 2.0;

2) 使用XmlListModel的时候我们需要添加 import QtQuick.XmlListModel 2.0

11.2 Porting to a mobile device 转到移动设备

把应用在Symbian Anna 或 Belle设备上运行很容易, 就像N9; 你可以使用template模板程序, 在QtCreator中创建一个新的项目; FIle->New File或Project, 在Application类目中选择Qt Quick Application(内建元素) 项目类型;

Note 这些步骤在QtCreator的project wizard中, 不同版本稍有不同;

wizard创建一个简单程序显示"Hello World", 包含C++代码和一些编译需要的文件;

可以用你的QML代码代替"Hello World"的QML:

- 把QML文件从WeatherClock文件夹包括Js, components, content. utils拷贝到项目文件夹下;

- 删除自动创建的main.qml, 把WeatherClock命名为main.qml;

- 把QML component和resources的相应目录设置好: 移除main.qml中的"../", 从./js/style.js中移除backgroundImage中的"../", 在Configure.qml中, 在source属性中的background值, 添加 "./ +"

-新的layout布局和尺寸要被裁剪为适合设备的350x640屏幕分辨率; e.g. NokiaN8; 如果设备有其他分辨率, 你需要调整尺寸相关的属性;

你可以在模拟器中编译运行, 观察portrait(肖像)和landscape(风景)模式;

11.3 Enhancements and New Features 功能增强以及新特性

-更好的configuration参数处理;

现在是把configuration参数保存在Configure组件中, 提供了一个UI; 所有的configuration改变在用户退出app时会丢失;

一个更好的实现应该是把Configure组件分成一个UI元素和一个configuration item; 后者的可以被其他需要使用configuration参数的item加载; 用户可以通过新的UI元素改变configuration参数;默认值加载以及在app退出时的保存, 可以由一个专用的item完成; 这个item使用了QtQuick的Offline Storage API/Qt Quick Local Storage QML Types; http://qt-project.org/doc/qt-4.8/qdeclarativeglobalobject.html 

“Qt Quick Application Developer Guide for Desktop”在4.2 Store and Load Data from a Database Using Javascript 解释了细节; 程序首次运行的时候, 一组默认值存储在了database中; 下一次启动的时候, database中的值可以被读取, 分配给configuration item相应的属性; 所有这些可以在main.qml的 onCompleted handler中完成; 我们可以在点击exitButton调用Qt.quit()之前, 存储当前的configuration参数;

-Internationalization国际化

新版本的应用可以在多语言环境使用; 我们已经使用了qsTr()宏; Google weather data可以使用多语言请求, 这省下很多工作; 不幸的是,, 这个应用在这有个小问题; 天气图标是根据天气情况用英语命名的; 如果天气数据是其他语言, 图标会找不到. 因为文件名和天气名字不匹配; 一个方案是将文件名改成URLs, 使用根据天气数据而定的默认图标, 就像本地图标根据文件名来定一样;

-Using Mobility APIs to get the current location automatically 使用Mobility APIs来自动获取当前位置;

如果程序在移动设备上运行, 可以使用Mobility API http://doc.qt.digia.com/qtmobility/index.html 代替预定义的位置, 来自动获取当前位置;

-Using other weather feeds 使用其他的天气源

支持多一种天气源可能是个好主意; 多数源需要注册, 多数作为商业用途的是收费的; 你可以为你这版的应用考虑其他的feeds; 

Weather Channel Weather APIs http://www.programmableweb.com/news/5-weather-apis-weatherbug-to-weather-channel/2009/04/15 

Weather Underground Authbrand http://www.wunderground.com/autobrand/info.asp 

Weather Underground Weather API http://www.wunderground.com/weather/api 

下一步

下一章;, 总结 

---11---


Chapter12 Lesson Learned and Further Reading 学习总结和扩展阅读

这个指南介绍了QtQuick的编程; 我们已经接触到了QtQuick的主要方面; 这些可以满足日常需求;

指南主要是帮助你开始了解, 指示哪里可以找到更多细节; 指南没有覆盖所有细节, 网络上可以看到Qt Document和其他资料;

有一个重点我们没有接触, 但是提到几次; 关于 C++扩展QtQuick, 给现有软件系统提供接口; 下面是相关文档:

QML for Qt Programmers http://qt-project.org/doc/qt-4.8/qtprogrammers.html 

Qt Quick - Introduction to Qt Quick http://qt-project.org/doc/qt-4.8/qtbinding.html

Extending QML Functionalities using C++  http://qt-project.org/doc/qt-4.8/qml-extending.html 

除了QtQuick example之外, 许多有趣的example在Qt培训材料中:

Qt Quick - Introduction to Qt Quick http://qt-project.org/videos/watch/qt_quick_introduction_to_qt_quick_part_1_4 

Qt Quick - Rapid User Interface Prototyping http://qt-project.org/videos/watch/qt_quick_rapid_user_interface_prototyping 

---12---

---YCR---