在这篇文章中,我们将详细介绍如何使用我们的Ubuntu SDK来从零开始来创建一个最基本的RSS阅读器.当我们完成整个练习后,我们将熟悉Ubuntu应用的整个开发流程.
特别提醒:在模拟器中参阅文章"怎么在Ubuntu手机中打开开发者模式"打开开发者模式,这样才可以把应用部署到模拟器中。如果我们想把自己的应用部署到我们的手机中,我们也需要在我们的手机中做同样的设置.
我们整个应用的将会像上面图片显示的那样.让我们现在马上开始吧.
在这里注意maintainer的格式。如果有红色的错误显示,请查看一下在“<”的左边有没有留有一个空格.
为了显示的更像一个是一个手机的界面,我们直接把“main.qml"中的尺寸设置如下:
width: units.gu(60) height: units.gu(85)
Device | Conversion |
Most laptops | 1 gu = 8 px |
Retina laptops | 1 gu = 16 px |
Smart phones | 1 gu = 18 px |
我们首先选择"Ubuntu SDK Desktop Kit"我们可以点击SDK屏幕左下方的绿色的运行按钮,或使用热键(Ctrl +R),运行应用。 如下图所示:
在我们的应用中,有一些代码是C++代码.我们先不要理会这些代码.让我们直接关注我们的QML文件.这些文件是用来构成我们UI的文件.最原始的应用其实没有什么。你可以按一下按钮改变方框中的文字。下面我们来开始设计我们的应用。
由于最初的代码其实对我们来书没有多大的用处。我们现在来修改我们的代码:
1)删除在"Main.qml"中不需要的代码,以使得代码如下图所示:
2)修改page中的title使之成为"POCO 摄影".重新运行我们的应用:
import QtQuick 2.4 import Ubuntu.Components 1.3 MainView { // objectName for functional testing purposes (autopilot-qt5) objectName: "mainView" // Note! applicationName needs to match the "name" field of the click manifest applicationName: "rssreader.liu-xiao-guo" /* This property enables the application to change orientation when the device is rotated. The default is false. */ //automaticOrientation: true width: units.gu(60) height: units.gu(85) PageStack { id: pageStack anchors.fill: parent Component.onCompleted: { console.log('pagestack created') pageStack.push(listPage) } Page { id: listPage title: i18n.tr("POCO 摄影") } } }
qml: pagestack created
import QtQuick 2.0 import QtQuick.XmlListModel 2.0 import Ubuntu.Components 1.3 import Ubuntu.Components.ListItems 1.0 Item { id: root signal clicked(var instance) anchors.fill: parent function reload() { console.log('reloading') model.clear(); pocoRssModel.reload() } ListModel { id: model } XmlListModel { id: pocoRssModel source: "http://my.poco.cn/rss_gen/poco_rss_channel.photo.php?sub_icon=figure" query: "/rss/channel/item[child::enclosure]" onStatusChanged: { if (status === XmlListModel.Ready) { for (var i = 0; i < count; i++) { // console.log("title: " + get(i).title); // console.log("published: " + get(i).published ); // console.log("image: " + get(i).image); console.log("image: " + get(i).content); var title = get(i).title.toLowerCase(); var published = get(i).published.toLowerCase(); var content = get(i).content.toLowerCase(); var word = input.text.toLowerCase(); if ( (title !== undefined && title.indexOf( word) > -1 ) || (published !== undefined && published.indexOf( word ) > -1) || (content !== undefined && content.indexOf( word ) > -1) ) { model.append({"title": get(i).title, "published": get(i).published, "content": get(i).content, "image": get(i).image }) } } } } XmlRole { name: "title"; query: "title/string()" } XmlRole { name: "published"; query: "pubDate/string()" } XmlRole { name: "content"; query: "description/string()" } XmlRole { name: "image"; query: "enclosure/@url/string()" } } GridView { id: gridview width: parent.width height: parent.height - inputcontainer.height clip: true cellWidth: parent.width/2 cellHeight: cellWidth + units.gu(1) x: units.gu(1.2) model: model delegate: GridDelegate {} Scrollbar { flickableItem: gridview } } Row { id:inputcontainer anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter height: units.gu(5) spacing:12 Icon { width: height height: parent.height name: "search" anchors.verticalCenter:parent.verticalCenter; } TextField { id:input placeholderText: "请输入关键词搜索:" width:units.gu(25) text:"" onTextChanged: { console.log("text is changed"); reload(); } } } }
import QtQuick 2.0 Item { width: parent.width /2 * 0.9 height: width Image { anchors.fill: parent anchors.centerIn: parent source: image fillMode: Image.PreserveAspectCrop MouseArea { anchors.fill: parent onClicked: { root.clicked(model); } } Text { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottomMargin: units.gu(1) horizontalAlignment: Text.AlignHCenter text: { return title.replace("[POCO摄影 - 人像]:", "");} clip: true color: "white" font.pixelSize: units.gu(2) } } }
ArticleGridView { id: articleList objectName: "articleList" anchors.fill: parent clip: true }
import "components"
qrc:///Main.qml:3:1: "components": no such directory
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickView> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view; view.setSource(QUrl(QStringLiteral("qrc:///Main.qml"))); view.setResizeMode(QQuickView::SizeRootObjectToView); view.show(); return app.exec(); }
<RCC> <qresource prefix="/"> <file>Main.qml</file> <file>components/GridDelegate.qml</file> <file>components/ArticleGridView.qml</file> <file>components/ArticleContent.qml</file> <file>components/ArticleListView.qml</file> <file>components/ListDelegate.qml</file> <file>components/images/arrow.png</file> <file>components/images/rss.png</file> </qresource> </RCC>如果我们需要的话,甚至可以使用文件编辑器对它直接进行编辑即可.更多关于Qt resource的介绍,可以参考文章" The Qt Resource System"
import QtQuick 2.0 import Ubuntu.Components 1.3 Item { property alias text: content.text Flickable { id: flickableContent anchors.fill: parent Text { id: content textFormat: Text.RichText wrapMode: Text.WordWrap width: parent.width } contentWidth: parent.width contentHeight: content.height clip: true } Scrollbar { flickableItem: flickableContent } }
signal clicked(var instance)
MouseArea { anchors.fill: parent onClicked: { root.clicked(model); } }
import QtQuick 2.4 import Ubuntu.Components 1.3 import "components" MainView { // objectName for functional testing purposes (autopilot-qt5) objectName: "mainView" // Note! applicationName needs to match the "name" field of the click manifest applicationName: "rssreader.liu-xiao-guo" width: units.gu(60) height: units.gu(85) PageStack { id: pageStack anchors.fill: parent Component.onCompleted: { console.log('pagestack created') pageStack.push(listPage) } Page { id: listPage title: i18n.tr("POCO 摄影") visible: false head.actions: [ Action { iconName: "reload" text: "Reload" onTriggered: articleList.reload() } ] ArticleGridView { id: articleList anchors.fill: parent clip: true onClicked: { console.log('[flat] article clicked: '+instance.title) articleContent.text = instance.content pageStack.push(contentPage) } } } Page { id: contentPage title: i18n.tr("Content") visible: false ArticleContent { id: articleContent objectName: "articleContent" anchors.fill: parent } } } Action { id: reloadAction text: "Reload" iconName: "reload" onTriggered: articleList.reload() } }
onClicked: { console.log('[flat] article clicked: '+instance.title) articleContent.text = instance.content pageStack.push(contentPage) }来捕获这个事件,并切换到另外一个叫做contentPage页面.
import QtQuick 2.0 import QtQuick.XmlListModel 2.0 import Ubuntu.Components 1.3 import Ubuntu.Components.ListItems 1.0 Item { id: root signal clicked(var instance) anchors.fill: parent function reload() { console.log('reloading') model.clear(); picoRssModel.reload() } ListModel { id: model } XmlListModel { id: picoRssModel source: "http://my.poco.cn/rss_gen/poco_rss_channel.photo.php?sub_icon=figure" query: "/rss/channel/item[child::enclosure]" onStatusChanged: { if (status === XmlListModel.Ready) { for (var i = 0; i < count; i++) { // console.log("title: " + get(i).title); // console.log("published: " + get(i).published ); // console.log("image: " + get(i).image); console.log("image: " + get(i).content); var title = get(i).title.toLowerCase(); var published = get(i).published.toLowerCase(); var content = get(i).content.toLowerCase(); var word = input.text.toLowerCase(); if ( (title !== undefined && title.indexOf( word) > -1 ) || (published !== undefined && published.indexOf( word ) > -1) || (content !== undefined && content.indexOf( word ) > -1) ) { model.append({"title": get(i).title, "published": get(i).published, "content": get(i).content, "image": get(i).image }) } } } } XmlRole { name: "title"; query: "title/string()" } XmlRole { name: "published"; query: "pubDate/string()" } XmlRole { name: "content"; query: "description/string()" } XmlRole { name: "image"; query: "enclosure/@url/string()" } } UbuntuListView { id: listView width: parent.width height: parent.height - inputcontainer.height clip: true visible: true model: model delegate: ListDelegate {} // Define a highlight with customized movement between items. Component { id: highlightBar Rectangle { width: 200; height: 50 color: "#FFFF88" y: listView.currentItem.y; Behavior on y { SpringAnimation { spring: 2; damping: 0.1 } } } } highlightFollowsCurrentItem: true focus: true // highlight: highlightBar Scrollbar { flickableItem: listView } PullToRefresh { onRefresh: { reload() } } } Row { id:inputcontainer anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter height: units.gu(5) spacing:12 Icon { width: height height: parent.height name: "search" anchors.verticalCenter:parent.verticalCenter; } TextField { id:input placeholderText: "请输入关键词搜索:" width:units.gu(25) text:"" onTextChanged: { console.log("text is changed"); reload(); } } } }
import QtQuick 2.0 Item { width: ListView.view.width height: units.gu(14) Row { spacing: units.gu(1) width: parent.width height: parent.height x: units.gu(0.2) Image { id: img property int borderLength: 2 source: image height: parent.height *.9 width: height anchors.verticalCenter: parent.verticalCenter } Column { id: right y: units.gu(1) anchors.leftMargin: units.gu(0.1) width: parent.width - img.width - parent.spacing spacing: units.gu(0.2) Text { text: { var txt = published.replace("GMT", ""); return txt; } font.pixelSize: units.gu(2) font.bold: true } Text { width: parent.width * .9 text: { var tmp = title.replace("[POCO摄影 - 人像]:", ""); if ( tmp.length > 35) return tmp.substring(0, 35) + "..."; else return tmp; } // wrapMode: Text.Wrap clip: true font.pixelSize: units.gu(2) } } } Image { source: "images/arrow.png" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.rightMargin: units.gu(0.6) rotation: -90 } MouseArea { anchors.fill: parent onClicked: { console.log("it is clicked"); console.log("currentidex: " + listView.currentIndex); console.log("index: " + index); listView.currentIndex = index; root.clicked(model); } } Keys.onReturnPressed: { console.log("Enter is pressed!"); listView.currentIndex = index; root.clicked(model); } }
Page { id: listPage title: i18n.tr("POCO 摄影") visible: false head.actions: [ Action { iconName: "reload" text: "Reload" onTriggered: articleList.reload() } ] ArticleListView { id: articleList anchors.fill: parent clip: true onClicked: { console.log('[flat] article clicked: '+instance.title) articleContent.text = instance.content pageStack.push(contentPage) } } }
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickView> #include <QStandardPaths> #include <QDebug> #include <QDir> #include <QQmlNetworkAccessManagerFactory> #include <QNetworkAccessManager> #include <QNetworkDiskCache> QString getCachePath() { QString writablePath = QStandardPaths:: writableLocation(QStandardPaths::DataLocation); qDebug() << "writablePath: " << writablePath; QString absolutePath = QDir(writablePath).absolutePath(); qDebug() << "absoluePath: " << absolutePath; absolutePath += "/cache/"; // We need to make sure we have the path for storage QDir dir(absolutePath); if ( dir.mkpath(absolutePath) ) { qDebug() << "Successfully created the path!"; } else { qDebug() << "Fails to create the path!"; } qDebug() << "cache path: " << absolutePath; return absolutePath; } class MyNetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: virtual QNetworkAccessManager *create(QObject *parent); }; QNetworkAccessManager *MyNetworkAccessManagerFactory::create(QObject *parent) { QNetworkAccessManager *nam = new QNetworkAccessManager(parent); QString path = getCachePath(); QNetworkDiskCache* cache = new QNetworkDiskCache(parent); cache->setCacheDirectory(path); nam->setCache(cache); return nam; } int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view; qDebug() << "Original factory: " << view.engine()->networkAccessManagerFactory(); qDebug() << "Original manager: " << view.engine()->networkAccessManager(); QNetworkDiskCache* cache = (QNetworkDiskCache*)view.engine()->networkAccessManager()->cache(); qDebug() << "Original manager cache: " << cache; view.engine()->setNetworkAccessManagerFactory(new MyNetworkAccessManagerFactory); view.setSource(QUrl(QStringLiteral("qrc:///Main.qml"))); view.setResizeMode(QQuickView::SizeRootObjectToView); view.show(); return app.exec(); }