Qt Quick 演示 - RSS 新闻
一个使用XmlListModel 和 XmlListModelRole 自定义 QML 类型来下载 XML 数据,以及 ListModel 和 ListElement 来创建类别列表,并使用 ListView 来显示数据的 QML RSS 新闻阅读器。
RSS 新闻 展示了以下 Qt Quick 功能
- 使用自定义 QML 类型。
- 使用列表模型和列表元素来表示数据。
- 使用 XML 列表模型来下载 XML 数据。
- 使用列表视图来显示数据。
- 使用 Component 类型为新闻项列表视图创建页脚。
- 使用 Image 类型创建关闭应用的按钮。
运行示例
要从 Qt Creator 运行示例,打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例。
使用自定义类型
在 RSS 新闻应用中,我们使用以下自定义类型,每个类型都定义在单独的 .qml 文件中
BusyIndicator.qml
CategoryDelegate.qml
NewsDelegate.qml
RssFeeds.qml
ScrollBar.qml
要使用自定义类型,我们在主 QML 文件 rssnews.qml 中添加一个 import 语句,导入名为 content
的文件夹,其中包含这些类型
import "./content"
创建主窗口
在 rssnews.qml
中,我们使用具有自定义属性的 Rectangle 类型来创建应用的主窗口
Rectangle { id: window width: 800 height: 480 property string currentFeed: rssFeeds.get(0).feed property bool loading: feedModel.status === XmlListModel.Loading property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation
我们将稍后使用这些自定义属性来加载 XML 数据和根据屏幕方向调整屏幕布局。
创建类别列表
在 rssnews.qml
中,我们使用在 RssFeeds.qml
中指定的 RssFeeds
自定义类型来创建一个内容类别列表
RssFeeds { id: rssFeeds }
在 RssFeeds.qml
中,我们使用 ListModel 类型和一个 ListElement 类型来创建一个类别列表,列表元素代表内容类别
ListModel { ListElement { name: "Top Stories"; feed: "news.yahoo.com/rss/topstories"; image: "images/TopStories.jpg" } ListElement { name: "World"; feed: "news.yahoo.com/rss/world"; image: "images/World.jpg" } ListElement { name: "Europe"; feed: "news.yahoo.com/rss/europe"; image: "images/Europe.jpg" } ListElement { name: "Asia"; feed: "news.yahoo.com/rss/asia"; image: "images/Asia.jpg" } ListElement { name: "U.S. National"; feed: "news.yahoo.com/rss/us"; image: "images/USNational.jpg" } ListElement { name: "Politics"; feed: "news.yahoo.com/rss/politics"; image: "images/Politics.jpg" } ListElement { name: "Business"; feed: "news.yahoo.com/rss/business"; image: "images/Business.jpg" } ListElement { name: "Technology"; feed: "news.yahoo.com/rss/tech"; image: "images/Technology.jpg" } ListElement { name: "Entertainment"; feed: "news.yahoo.com/rss/entertainment"; image: "images/Entertainment.jpg" } ListElement { name: "Health"; feed: "news.yahoo.com/rss/health"; image: "images/Health.jpg" } ListElement { name: "Science"; feed: "news.yahoo.com/rss/science"; image: "images/Science.jpg" } ListElement { name: "Sports"; feed: "news.yahoo.com/rss/sports"; image: "images/Sports.jpg" } }
列表元素定义与其他 QML 类型类似,只是它们包含一系列 role 定义而不是属性。角色既定义了数据如何被访问,也包含了数据本身。
对于每个列表元素,我们使用 name
角色指定类别名称,使用 feed
角色指定加载数据的 URL,并使用 image
角色显示类别的图像。
在 rssnews.qml
中,我们使用 ListView 类型来显示类别列表
ListView { id: categories property int itemWidth: 190 width: isPortrait ? parent.width : itemWidth height: isPortrait ? itemWidth : parent.height orientation: isPortrait ? ListView.Horizontal : ListView.Vertical anchors.top: parent.top model: rssFeeds delegate: CategoryDelegate { itemSize: categories.itemWidth } spacing: 3 }
要将类别列表水平布置在纵向窗口的顶部和横向模式下的左侧,我们使用 orientation
属性。根据方向,我们将列表的宽度或高度绑定到固定值(itemWidth
)。
我们使用 anchors.top
属性在两种方向中都将列表视图定位在屏幕的顶部。
我们使用 model
属性从 rssFeeds
模型中加载 XML 数据,并将 CategoryDelegate
作为代理来创建列表中的每个项目。
创建列表元素
在 CategoryDelegate.qml
中,我们使用带自定义属性的 Rectangle 类型来创建列表元素
Rectangle { id: delegate property bool selected: ListView.isCurrentItem
我们将 selected
属性设置为 ListView.isCurrentItem
动态属性,以指定如果 delegate
是当前项,则 selected
为 true
。
我们使用 Image 类型的 source
属性显示图像,在代理中居中,由 rssFeeds
列表模型中 image
角色指定的列表元素指定
Image { anchors.centerIn: parent source: image }
我们使用 Text 类型为列表元素添加标题
Text { id: titleText anchors { left: parent.left; leftMargin: 20 right: parent.right; rightMargin: 20 top: parent.top; topMargin: 20 } font { pixelSize: 18; bold: true } text: name color: selected ? "#ffffff" : "#ebebdd" scale: selected ? 1.15 : 1.0 Behavior on color { ColorAnimation { duration: 150 } } Behavior on scale { PropertyAnimation { duration: 300 } }
我们使用 anchors
属性将标题定位在列表元素的顶部,并具有 20 像素的边距。我们使用 font
属性调整字体大小和文本格式。
我们使用 color
和 scale
属性来增强文本亮度,并在列表项是当前项时将其稍微放大。通过将 Behavior 应用到属性,我们可以动画化选择和取消选择列表项的动作。
我们使用 MouseArea 类型,当用户点击类别列表元素时下载 XML 数据
MouseArea { anchors.fill: delegate onClicked: { delegate.ListView.view.currentIndex = index if (window.currentFeed == feed) feedModel.reload() else window.currentFeed = feed } }
将 anchors.fill
属性设置为 delegate
以使用户能够在列表元素内点击任何地方。
我们使用 onClicked
信号处理程序加载类别列表的 XML 数据。如果已点击的类别是当前项目,将调用 reload()
函数重新加载数据。
下载 XML 数据
在 rssnews.qml
中,我们使用 XmlListModel 类型作为 ListView 元素的数据源,以显示选定类别中的新闻条目
XmlListModel { id: feedModel source: "https://" + window.currentFeed query: "/rss/channel/item"
我们使用 source
属性和 window.currentFeed
自定义属性来获取选定类别中的新闻条目。
属性 query
指定 XmlListModel 为 XML 文档中的每个 <item>
生成一个模型项
我们使用 XmlListModelRole 类型来指定模型项属性。每个模型项都有与 XML 文档中相应的 <item>
匹配的 title
、content
、link
和 pubDate
属性
XmlListModelRole { name: "title"; elementName: "title"; attributeName: ""} XmlListModelRole { name: "content"; elementName: "content"; attributeName: "url" } XmlListModelRole { name: "link"; elementName: "link"; attributeName: "" } XmlListModelRole { name: "pubDate"; elementName: "pubDate"; attributeName: "" } }
我们使用 feedModel
模型并在 ListView
类型中显示数据
ListView { id: list anchors.left: isPortrait ? window.left : categories.right anchors.right: closeButton.left anchors.top: isPortrait ? categories.bottom : window.top anchors.bottom: window.bottom anchors.leftMargin: 30 anchors.rightMargin: 4 clip: isPortrait model: feedModel footer: footerText delegate: NewsDelegate {} }
要将新闻条目列在分类列表下方,在竖屏方向下位于其右侧,在横屏方向下位于其右侧,我们使用isPortrait
自定义属性来将新闻条目列表的顶部锚定到竖屏方向下的window
的左侧和categories
的底部,横屏方向下则锚定到categories
的右侧和window
的底部。
我们使用anchors.bottom
属性将列表视图的底部锚定到两种方向下的窗口底部。
在竖屏方向下,我们将新闻条目的绘制剪裁到列表视图的边界矩形中,以避免新闻条目滚动到其他项目上时的图形失真。在横屏方向下,这不需要,因为列表纵向横跨整个屏幕。
我们使用model
属性从feedModel
模型中加载XML数据,并使用NewsDelegate
作为委托实例化列表中的每个项目。
在NewsDelegate.qml
中,我们使用Column类型来布局XML数据
Column { id: delegate width: delegate.ListView.view.width spacing: 8
在列内部,我们使用Row和其他列来定位图片和标题文字
Row { width: parent.width spacing: 8 Column { Item { width: 4 height: titleText.font.pixelSize / 4 } Image { id: titleImage source: content width: Math.min(delegate.width / 2, sourceSize.width) fillMode: Image.PreserveAspectFit } } Text { id: titleText text: title.replace(/'/g, "'") width: delegate.width - titleImage.width wrapMode: Text.WordWrap font.pixelSize: 26 font.bold: true } }
我们使用timeSinceEvent()
JavaScript函数生成一个文本表示,表示该项目已发布多长时间
Text { width: delegate.width font.pixelSize: 12 textFormat: Text.RichText font.italic: true text: timeSinceEvent(pubDate) + " (<a href=\"" + link + "\">Link</a>)" onLinkActivated: function(link) { Qt.openUrlExternally(link) } }
我们使用onLinkActivated
信号处理程序在用户选择链接时在外部浏览器中打开URL。
向用户提供反馈
在CategoryDelegate.qml
中,我们使用自定义的BusyIndicator
类型来在加载XML数据时表示活动状态
BusyIndicator { scale: 0.8 visible: delegate.ListView.isCurrentItem && window.loading anchors.centerIn: parent }
我们使用scale
属性将指示器大小减少到0.8
。我们将visible
属性绑定到delegate
列表视图的isCurrentItem
附加属性和主窗口的loading
属性,以在分类项目是当前项目且XML数据正在加载时显示指示器图像。
在BusyIndicator.qml
中,我们定义BusyIndicator
类型。我们使用Image类型来显示图像,并对其rotation
属性应用NumberAnimation以旋转图像,使其无限循环
Image { id: container source: "images/busy.png"; NumberAnimation on rotation { running: container.visible from: 0; to: 360; loops: Animation.Infinite; duration: 1200 } }
在您的应用程序中,您还可以使用来自Qt Quick Controls模块的BusyIndicator类型。
创建滚动条
在rssnews.qml
中,我们使用自己的自定义ScrollBar
类型在分类和新闻条目列表视图中创建滚动条。在您的应用程序中,您还可以使用来自Qt Quick Controls模块的ScrollView类型。
首先,我们在分类列表视图中创建一个滚动条。我们将orientation
属性绑定到isPortrait
属性和Qt::Orientation
枚举类型的Horizontal
值,以在竖屏方向下显示水平滚动条,并使用Vertical
值以在横屏方向下显示垂直滚动条
ScrollBar { id: listScrollBar orientation: isPortrait ? Qt.Horizontal : Qt.Vertical height: isPortrait ? 8 : categories.height; width: isPortrait ? categories.width : 8 scrollArea: categories; anchors.right: categories.right }
与分类列表视图类似,我们根据isPortrait
属性调整滚动条的宽度和高度。
我们使用scrollArea
属性在categories
列表视图中显示滚动条。
我们使用anchors.right
属性将滚动条锚定到分类列表的右侧。
ScrollBar { scrollArea: list width: 8 anchors.right: window.right anchors.top: isPortrait ? categories.bottom : window.top anchors.bottom: window.bottom }
其次,我们在新闻项目列表视图中创建另一个滚动条。我们希望无论屏幕方向如何都能在视图的右侧显示垂直滚动条,因此我们可以将width
属性设置为8
,并将anchors.right
属性绑定到window.right
属性。我们使用anchors.top
属性将滚动条顶部锚定于纵向方向中类别列表的底部和横向方向中新闻项目列表的顶部。我们使用anchors.bottom
属性将滚动条底部在两个方向中均锚定到视图底部。
我们在ScrollBar.qml
中定义了ScrollBar
类型。我们使用自定义属性的Item类型来创建滚动条的容器
Item { id: container property variant scrollArea property int orientation: Qt.Vertical opacity: 0
我们使用BorderImage类型来显示滚动条滑块,滑块的位置由我们通过使用position()
函数计算得到
BorderImage { source: "images/scrollbar.png" border { left: 1; right: 1; top: 1; bottom: 1 } x: container.orientation == Qt.Vertical ? 2 : position() y: container.orientation == Qt.Vertical ? position() : 2 width: container.orientation == Qt.Vertical ? container.width - 4 : size() height: container.orientation == Qt.Vertical ? size() : container.height - 4 }
我们使用size
函数根据屏幕方向计算滑块的宽度和高度。
我们使用states
来在用户移动滚动区域时使滚动条可见
states: State { name: "visible" when: container.orientation == Qt.Vertical ? scrollArea.movingVertically : scrollArea.movingHorizontally PropertyChanges { target: container; opacity: 1.0 } }
我们使用transitions
在状态从"visible"
变为默认状态时应用到opacity
属性的NumberAnimation
transitions: Transition { from: "visible"; to: "" NumberAnimation { properties: "opacity"; duration: 600 } } }
创建页脚
在rssnews.qml
中,我们使用带有Rectangle类型的Component类型来为新闻列表视图创建页脚
Component { id: footerText Rectangle { width: parent.width height: closeButton.height color: "lightgray" Text { text: "RSS Feed from Yahoo News" anchors.centerIn: parent font.pixelSize: 14 } } }
我们将页脚的width
绑定到组件的宽度,height
绑定到关闭按钮的高度,以便在没有显示新闻项目时对齐它们。
创建按钮
在rssnews.qml
中,我们使用Image类型创建一个简单的可点击按钮,用户可以点击它来关闭应用程序
Image { id: closeButton source: "content/images/btn_close.png" scale: 0.8 anchors.top: parent.top anchors.right: parent.right anchors.margins: 4 opacity: (isPortrait && categories.moving) ? 0.2 : 1.0 Behavior on opacity { NumberAnimation { duration: 300; easing.type: Easing.OutSine } } MouseArea { anchors.fill: parent onClicked: { Qt.quit() } } }
我们使用anchors
将关闭按钮定位到新闻列表视图的右上角,边缘为4像素。因为关闭按钮在纵向方向中与类别列表重叠,所以当用户在类别列表中滚动时,我们会对opacity
属性进行动画处理,使按钮几乎完全透明。
我们使用位于MouseArea内的onClicked
信号处理器,当用户选择关闭按钮时调用quit()
函数。
另请参阅QML应用程序。
© 2024 The Qt Company Ltd. 此处包含的文档贡献的版权归各自的拥有者。本提供的文档是根据由自由软件基金会发布的GNU自由文档许可证版本1.3的条款进行许可的。Qt及其相关标志是The Qt Company Ltd.在芬兰及/或全世界范围的商标。所有其他商标均为其各自所有者的财产。