在该脚本中,您将学习如何在手机上为Ubuntu编写货币换算器应用程序。您将使用到Ubuntu QML工具包中的数个组件:i18nunits、用于设置主题的ItemStyleLabelActivityIndicatorPopoverButtonTextFieldListItems.HeaderListItems.Standard

该应用程序将为您说明如何使用QML说明性语言创建运作正常的用户界面及其逻辑,以及如何进行网络通信和从Internet上的远程数据源提取数据。

在实践中,您将编写一个可在选中的两种货币之间执行货币换算操作的应用程序。汇率将通过欧洲中央银行的API提取。通过按下按钮,从列表中选择所需的货币,即可完成货币换算。

要求

工具

本教程的重点是Ubuntu UI工具包预览版及其组件,而不是工具。但是,我们将在教程中提到您将使用到的工具,并对其进行简要概述:

开发主机

Ubuntu 12.04(或更高版本)将用作开发主机。在本脚本结束时,您将创建出一个可在开发主机上运行的平台中立性QML应用程序。不同架构和手机安装版本的交叉编译等主题是更为高级的主题,我们将在稍后发布完整的Ubuntu SDK版本后涉及这些主题。

集成开发环境(IDE)

我们将编写说明性的QML代码,这些代码的编译不以执行为目的,这样您可使用您最爱的文本编辑器来编写将组成单一QML文件的实际代码。

对于本教程,我们建议使用Ubuntu SDK。Ubuntu SDK是一个强大的IDE,可基于Qt框架开发应用程序。

QML查看器

要启动QML应用程序(不管是在构建原型期间还是最终阶段),我们都会使用Ubuntu SDKCtrl+R 快捷键

但是,作为使用QML Scene快速查看应用程序的备用方法,值得注意的是,您也可使用不带Ubuntu SDK的QML Scene。QML Scene是一个负责解释和运行QML代码的命令行应用程序。

要使用QML Scene运行QML应用程序,可按下Ctrl+Alt+T组合键打开终端,执行qmlscene命令,然后输入应用程序路径:

$ qmlscene /path/to/application.qml

详细了解QML Scene

入门

要启动Ubuntu SDK,只需打开仪表板,开始输入“ubuntu sdk“,然后单击搜索结果中出现的Ubuntu SDK图标。

下一站:成为我们的的开发人员。

主视图

我们将使用我们的首个Ubuntu组件启动构建最小的QML canvas:主视图内的一个标签。

  1. 在Ubuntu SDK中,按下Ctrl+N来创建新项目
  2. 依次选择Projects > Ubuntu > Simple UI模板然后单击Choose…
  3. CurrencyConverter作为项目的名称。您可将Create in:字段保留为默认字段,然后单击Next
  4. 您可选择在最后步骤中设立Bazaar等的修正控制系统,但这不属于本教程的指导Scope。单击Finish
  5. 替换Column组件及其所有子组件,用如下所示的Page进行替换,然后进行保存(使用 Ctrl+S):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import QtQuick 2.0
import Ubuntu.Components 0.1
 
/*!
    brief MainView with a Label and Button elements.
*/
 
MainView {
    id: root
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"
    applicationName: "CurrencyConverter"
 
    width: units.gu(100)
    height: units.gu(75)
 
    property real margins: units.gu(2)
    property real buttonWidth: units.gu(9)
 
    Page {
        title: i18n.tr("Currency Converter")
 
    }
}

现在尝试运行,以查看结果:

  1. 在Ubuntu SDK内部,按下Ctrl+R组合键。这是前往Build > Run菜单项的快捷键组合

或者,可从终端启动:

  1. 使用Ctrl+Alt+T打开一个终端
  2. 运行以下命令:

qmlscene ~/CurrencyConverter/CurrencyConverter.qml

祝贺您!您的首个手机Ubuntu应用程序已完成并启动运行。虽然这不是特别令人新奇的事,但您会发现自己编写应用程序是非常简单的。您现在即可关闭应用程序。

现在,让我们从文件开头开始浏览代码。

1
2
import QtQuick 2.0
import Ubuntu.Components 0.1

每个QML文件都包含两个部分:导入部分和声明对象部分。首先,我们导入需要的QML类型和组件,指定命名空间及其版本。在我们的教程中,我们导入内置QML和Ubuntu类型及组件。

我们现在开始声明对象。在QML中,用户界面指定为带有属性的对象树。JavaScript也可内置为QML中的脚本语言,我们稍后将看到相关示例。

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MainView {
    id: root
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"
    applicationName: "CurrencyConverter"
 
    width: units.gu(100)
    height: units.gu(75)
 
    property real margins: units.gu(2)
    property real buttonWidth: units.gu(9)
 
    Page {
        title: i18n.tr("Currency Converter")
 
    }
}

其次,我们会创建MainView,这是SDK最重要的组件,将作为应用程序的根容器。该组件还将提供标准ToolbarHeader

使用与JSON类似的语法,我们定义其属性(通过为其提供一个可称为(root)的ID),然后我们将定义一些视觉属性(widthheightcolor)。注意QML中的属性如何与“属性:值”语法中的值绑定。我们也会定义一个称为margins的自定义属性,该属性属于typereal(带小数点的数字)。现在不必担心buttonWidth属性,我们稍后会使用该属性。对于MainView中的其余可用属性,我们通过不声明属性的方法保留其默认值。

注意我们如何指定单位为units.gu。这些是栅格单元,我们稍后马上会讲到。现在,您可将这些栅格单元视作指定评估值值的与外观设置无关的方法。它们返回根据应用程序正在其上运行的设备而定的像素值。

在主视图内,我们添加一个子Page,其中会包含剩余的组件并会提供标题。我们为该页的文本添加设置标题,确保其包含可使其完成转换的i18n.tr()函数。

 

分辨率无关性

Ubuntu用户界面工具包的一个主要功能是在具有多个设备的用户设定的环境里调整外观设置的能力。一直采取的方法是定义新单元类型,即栅格单元(缩写为gu)。栅格单元根据屏幕类型和应用程序在其上运行的设备转换为像素值。以下是一些示例:

设备 换算
大多数笔记本计算机 1 gu = 8 px
Retina笔记本计算机 1 gu = 16 px
智能手机 1 gu = 18 px

详细了解分辨率无关性

国际化

遵照Ubuntu理念,国际化和本国语言支持是Ubuntu工具包的一项主要功能。我们已选择将gettext作为最具普遍性的免费软件国际化技术,并通过整个i18n.tr()函数系列在QML中实施了该技术。

提取和换算货币

现在我们将启动添加逻辑到应用程序,这将意味着获取货币和汇率数据,以及执行实际换算。

首先在Page右大括弧之前的23行周围添加以下代码。我们通常会在所有后续步骤中追加代码,但任何代码片段都会包含在我们的根MainView内。因此,在您追加代码时,确保其仍然位于文件末尾的MainView右大括弧之前。

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ListModel {
    id: currencies
    ListElement {
        currency: "EUR"
        rate: 1.0
    }
 
    function getCurrency(idx) {
        return (idx >= 0 && idx < count) ? get(idx).currency: ""
    }
 
    function getRate(idx) {
        return (idx >= 0 && idx < count) ? get(idx).rate: 0.0
    }
}

我们在此处要执行的操作是:将货币用作一个ListModel对象,其中将包含一个由currencyrate对组成的项目列表。货币ListModel将用作一个将显示数据的视图元素源。我们将从欧洲中央银行的欧洲外汇兑换参考汇率中提取真实数据。同样,欧洲并未在此处进行定义,因此我们将用欧元汇率预填充列表,参考汇率为1.0。

汇率中的函数语句说明QML的另一项强大功能:与JavaScript集成。两项JavaScript功能用作粘合代码,以从索引中检索货币或汇率。这两项功能为必要功能,因为组件属性绑定在首次使用货币时可能无法完成加载。但不要过多担心它们的函数。现在,唯一重要的是,记住您可透明地集成您QML对象中的JavaScript代码

现在,我们将使用QtQuick对象提取真实数据,以将XML数据加载到模式:XmlListModel中。要使用该对象,我们在文件开头添加一条额外的导入语句,因此看起来就像是:

1
2
3
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1

随后,在39行周末,添加实际汇率兑换提取器代码:

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
XmlListModel {
    id: ratesFetcher
    namespaceDeclarations: "declare namespace gesmes='http://www.gesmes.org/xml/2002-08-01';"
                           +"declare default element namespace 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref';"
    query: "/gesmes:Envelope/Cube/Cube/Cube"
 
    onStatusChanged: {
        if (status === XmlListModel.Ready) {
            for (var i = 0; i < count; i++)
                currencies.append({"currency": get(i).currency, "rate": parseFloat(get(i).rate)})
        }
    }
 
    XmlRole { name: "currency"; query: "@currency/string()" }
    XmlRole { name: "rate"; query: "@rate/string()" }
}

相关属性是source,以指出从中提取数据的URL;query,以指定绝对XPath查询,从而将其用作创建下方XmlRoles的模型项目的基础查询;以及将namespaceDeclarations作为用于XPath查询中的命名空间声明。

onStatusChanged信号处理函数显示多种功能的其他组合:信号和处理函数系统与JavaScript的组合。每个QML属性都有一个<property>Changed信号,以及<property>Changed信号处理函数上的一个对应值。在这种情况中,系统将发出StatusChanged信号,以通知状态属性的任何变动,我们定义一个处理函数来追加所有货币/汇率项目到currenciesListModel(只要ratesFetcher已完成数据加载)。

总的来说,ratesFetcher将填充上货币/汇率项目,这些项目随后将追加到currencies上。

需注意的是,大多数情况下,我们可以将单个ListModel用作数据源,但在本教程中,我们将其用作中间容器。我们需要修改数据以添加欧元货币,然后将结果放入currenciesListModel中。

注意网络访问如何透明地发生,以便作为开发人员的您完全可以不必考虑这件事!

在56行周围,添加ActivityIndicator组件以显示活动,同时提取汇率:

56
57
58
59
ActivityIndicator {
    anchors.right: parent.right
    running: ratesFetcher.status === XmlListModel.Loading
}

我们将该组件固定在其母(root)右侧,它在提取汇率数据之前将一直显示活动。

最后,在 66行周围(Page以上和以外,我们添加convertJavaScript函数,该函数将执行真实的货币换算:

21
22
23
24
25
26
function convert(from, fromRateIndex, toRateIndex) {
    var fromRate = currencies.getRate(fromRateIndex);
    if (from.length <= 0 || fromRate <= 0.0)
        return "";
    return currencies.getRate(toRateIndex) * (parseFloat(from) / fromRate);
}

选择货币

此时,我们已添加了所有后端代码,我们将开始讲解用户互动。我们将首先创建一个新组件,一个通过组合其他组件和对象创建而成的重复使用块。

让我们先在文件开头追加两个导入语句,这两个语句位于其他导入语句下方:

4
5
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1

然后,在68行周围添加以下代码:

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
Component {
    id: currencySelector
    Popover {
        Column {
            anchors {
                top: parent.top
                left: parent.left
                right: parent.right
            }
            height: pageLayout.height
            Header {
                id: header
                text: i18n.tr("Select currency")
            }
            ListView {
                clip: true
                width: parent.width
                height: parent.height - header.height
                model: currencies
                delegate: Standard {
                    text: currency
                    onClicked: {
                        caller.currencyIndex = index
                        caller.input.update()
                        hide()
                    }
                }
            }
        }
    }
}

此刻,如果您运行应用程序,您还看不到任何明显的变化,因此,不必担心是否只看到空矩形。

我们已基于Popover和标准Qt QuickListView创建货币选择器。ListView将显示来自currenciesListMode的数据。注意Column对象如何包装Header和列表视图以便实现垂直排列,以及列表视图中的每一个项目如何成为Standard列表项目组件。

popover将显示货币选择结果。完成选择后,popover将隐藏(见onClicked 信号),呼叫者的数据将完成更新。我们假设呼叫者拥有currencyIndexinput属性,input是拥有update()函数的一个项目。

安排UI

截至目前,我们已为货币换算应用程序设置了后端并构建了块。让我们开始完成充满乐趣的最后一步,将组件组合到一起,看看结果如何!

在行99周围添加代码的最终片段:

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
Column {
    id: pageLayout
 
    anchors {
        fill: parent
        margins: root.margins
    }
    spacing: units.gu(1)
 
    Row {
        spacing: units.gu(1)
 
        Button {
            id: selectorFrom
            property int currencyIndex: 0
            property TextField input: inputFrom
            text: currencies.getCurrency(currencyIndex)
            onClicked: PopupUtils.open(currencySelector, selectorFrom)
        }
 
        TextField {
            id: inputFrom
            errorHighlight: false
            validator: DoubleValidator {notation: DoubleValidator.StandardNotation}
            width: pageLayout.width - 2 * root.margins - root.buttonWidth
            height: units.gu(5)
            font.pixelSize: FontUtils.sizeToPixels("medium")
            text: '0.0'
            onTextChanged: {
                if (activeFocus) {
                    inputTo.text = convert(inputFrom.text, selectorFrom.currencyIndex, selectorTo.currencyIndex)
                }
            }
            function update() {
                text = convert(inputTo.text, selectorTo.currencyIndex, selectorFrom.currencyIndex)
            }
        }
    }
 
    Row {
        spacing: units.gu(1)
        Button {
            id: selectorTo
            property int currencyIndex: 1
            property TextField input: inputTo
            text: currencies.getCurrency(currencyIndex)
            onClicked: PopupUtils.open(currencySelector, selectorTo)
        }
 
        TextField {
            id: inputTo
            errorHighlight: false
            validator: DoubleValidator {notation: DoubleValidator.StandardNotation}
            width: pageLayout.width - 2 * root.margins - root.buttonWidth
            height: units.gu(5)
            font.pixelSize: FontUtils.sizeToPixels("medium")
            text: '0.0'
            onTextChanged: {
                if (activeFocus) {
                    inputFrom.text = convert(inputTo.text, selectorTo.currencyIndex, selectorFrom.currencyIndex)
                }
            }
            function update() {
                text = convert(inputFrom.text, selectorFrom.currencyIndex, selectorTo.currencyIndex)
            }
        }
    }
 
    Button {
        text: i18n.tr("Clear")
        width: units.gu(12)
        onClicked: {
            inputTo.text = '0.0';
            inputFrom.text = '0.0';
        }
    }
}

这是长于之前代码片段的一片代码,但非常简单,没有多少新语法。我们要做的是安排视觉组件以在root区域范围内提供用户互动,以及定义信号处理函数。

注意我们如何使用onClicked信号处理函数来定义用户单击货币选择器时将发生的操作(即弹出框被打开),如何使用onTextChanged处理函数调用之前定义的convert()函数以在输入过程中执行换算,以及我们如何定义update()()函数列表视图项目(这些项目来自之前计划定义的currencySelector组件)。

我们使用一列和两行来设置布局,每行包含一个货币选择器按钮和一个文本字段,以展示或输入货币换算值。我们也已经在它们下方添加一个按钮,以便一次性清除两个文本字段。以下是说明布局的实体模型:

 

请看

这就行了!现在,我们可以轻松享受自己创建的应用程序了。只需按下Ubuntu SDK内的Ctrl+R快捷键,将看到您仅通过几行代码就完成创建的功能完整、时尚的货币换算器。

 

结论

您已了解如何编写与外观设置无关的Ubuntu手机应用程序。为此,您利用和组织QML、Javascript等技术以及多种Ubuntu组件的的优势,创建一个具备紧凑、简洁的Ubuntu外观的应用程序。

您一定会注意到这些技术具有多种不同的排列可能性,因此现在一切取决于您:帮助我们测试工具包预览版,编写自己的应用程序,向我们提供您的反馈,以帮助Ubuntu实现在未来10亿手机中的置入!

了解更多

如果本教程已引起您的兴趣,您一定要看看带有Ubuntu QML工具包预览版的组件陈列窗应用程序。有了该应用程序,您将能够看到所有工作中的Ubuntu组件,查询它们的代码以了解如何在应用程序中使用它们。

 

如果您希望学习组件陈列窗代码:

  1. 按下启动程序中的Ubuntu按纽,启动Ubuntu SDK。随后将出现仪表板
  2. 开始输入ubuntu sdk,然后单击Ubuntu SDK图标。
  3. 在Ubuntu SDK中,然后按下Ctrl+O组合键以打开文件选择对话框。
  4. 选择文件/usr/lib/ubuntu-ui-toolkit/examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml并单击Open
  5. 要运行代码,可依次选择Tools > External > Qt Quick > Qt Quick 2 Preview (qmlscene)菜单项。

或者,如果您只想运行组件陈列窗:

  1. 打开仪表板
  2. 输入“toolkit gallery”,双击出现的“Ubuntu Toolkit Gallery”以运行该应用程序。

参考

有任何问题吗?

如果您对本教程或其使用的技术有任何问题,只需联系我们的应用程序开发人员社区即可!