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