Qt Quick 演示 - RSS 新闻
这是一个基于 QML 的 RSS 新闻阅读器,它使用XmlListModel 和XmlListModelRole 中的自定义 QML 类型来下载 XML 数据,使用ListModel 和ListElement 来创建分类列表,并使用ListView 来显示数据。

RSS News演示了以下 Qt Quick 功能:
- 使用自定义 QML 类型。
- 使用列表模型和列表元素来表示数据。
- 使用 XML 列表模型下载 XML 数据。
- 使用列表视图显示数据。
- 使用Component 类型为新闻条目列表视图创建页脚。
- 使用Image 类型创建用于关闭应用程序的按钮。
运行示例
您可以通过以下方式运行示例:
- Qt Creator
打开Welcome 模式,并从Examples 中选择该示例。有关更多信息,请参阅Qt Creator :教程:构建和运行。
- Qt Extension for Visual Studio Code
在Command Palette 中运行Qt: Open Qt examples 命令,并从列表中选择该示例。有关更多信息,请参阅Qt Extension for Visual Studio Code :教程:构建和运行。
使用自定义类型
在 RSS 新闻应用中,我们使用了以下自定义类型,每种类型都在单独的 .qml 文件中定义:
BusyIndicator.qmlCategoryDelegate.qmlNewsDelegate.qmlRssFeeds.qmlScrollBar.qml
创建主窗口
在Main.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数据,并根据屏幕方向调整布局。
创建分类列表
在Main.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 类型类似,不同之处在于它们包含一组角色定义,而非属性。角色既定义了数据的访问方式,也包含数据本身。
对于每个列表元素,我们使用name 角色来指定类别名称,使用feed 角色来指定加载数据的URL,并使用image 角色来显示该类别的图片。
在 `Main.qml` 中,我们使用 `ListView ` 类型来显示类别列表:
ListView {
id: categories
property int itemWidth: 190
width: window.isPortrait ? parent.width : itemWidth
height: window.isPortrait ? itemWidth : parent.height
orientation: window.isPortrait ? ListView.Horizontal : ListView.Vertical
anchors.top: parent.top
model: rssFeeds
delegate: CategoryDelegate {
itemSize: categories.itemWidth
isLoading: window.loading
onClicked: function () {
if (window.currentFeed == feed)
feedModel.reload()
else
window.currentFeed = feed
}
}为了在纵向模式下将类别列表水平布局在窗口顶部,在横向模式下垂直布局在左侧,我们使用orientation 属性。根据屏幕方向,我们将列表的宽度或高度绑定到一个固定值(itemWidth )。
我们使用anchors.top 属性,使列表视图在两种屏幕方向下均位于屏幕顶部。
我们使用model 属性从rssFeeds 模型加载XML数据,并将CategoryDelegate 作为委托,用于在列表中实例化每个项目。
创建列表元素
在 `CategoryDelegate.qml` 中,我们使用带有自定义属性的 `Rectangle ` 类型来创建列表元素:
Rectangle {
id: delegate
required property real itemSize
required property string name
required property string feed
required property string image
required property int index
required property bool isLoading
property bool selected: ListView.isCurrentItem我们使用 `required ` 来声明模型中预期的属性。
我们将selected 属性设置为ListView.isCurrentItem 的附加属性,以指定当delegate 为当前项时,selected 应为true 。
我们使用Image 类型的source 属性,以在委托中居中显示该图像,该图像由rssFeeds 列表模型中image 角色为列表元素指定:
Image {
anchors.centerIn: parent
source: delegate.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: delegate.name
color: delegate.selected ? "#ffffff" : "#ebebdd"
scale: delegate.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 = delegate.index
delegate.clicked()
}
}将anchors.fill 属性设置为delegate ,以便用户可在列表元素内的任意位置点击。
为了在clicked 事件发生时加载类别列表的XML数据,我们将ListView 附加属性的currentIndex 属性设置为 ,并在onClicked 中触发clicked 信号()。
下载 XML 数据
在Main.qml 中,我们使用XmlListModel 类型作为ListView 元素的数据源,以显示所选类别中的新闻条目:
XmlListModel {
id: feedModel
source: "https://" + window.currentFeed
query: "/rss/channel/item"我们使用source 属性以及window.currentFeed 自定义属性来获取所选类别的新闻条目。
query 属性指定XmlListModel 为XML文档中的每个<item> 生成一个模型项。
我们使用 `XmlListModelRole ` 类型来指定模型项的属性。每个模型项都具有 `title`、`content`、`link` 和 `pubDate ` 属性,这些属性与 XML 文档中相应 `<item> ` 的值相对应:
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: window.isPortrait ? window.left : categories.right
anchors.right: closeButton.left
anchors.top: window.isPortrait ? categories.bottom : window.top
anchors.bottom: window.bottom
anchors.leftMargin: 30
anchors.rightMargin: 4
clip: window.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
required property string title
required property string content
required property string link
required property string pubDate
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: delegate.content
width: Math.min(delegate.width / 2, sourceSize.width)
fillMode: Image.PreserveAspectFit
}
}
Text {
id: titleText
text: delegate.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: delegate.timeSinceEvent(delegate.pubDate) + " (<a href=\"" + delegate.link + "\">Link</a>)"
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
}当用户点击链接时,我们使用onLinkActivated 信号处理程序在外部浏览器中打开该URL。
向用户提供反馈
在CategoryDelegate.qml 中,我们使用BusyIndicator 自定义类型来指示XML数据加载过程中的活动状态:
BusyIndicator {
scale: 0.8
visible: delegate.ListView.isCurrentItem && delegate.isLoading
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
}
}BusyIndicator 在您的应用中,还可以使用来自 Qt Quick Controls 模块中的xml-ph-0000@deepl.internal类型。
创建滚动条
ScrollView 在Main.qml 中,我们使用自定义的ScrollBar 类型在分类和新闻条目列表视图中创建滚动条。在您的应用中,您也可以使用来自 Qt Quick Controls 模块中的xml-ph-0002@deepl.internal类型。
首先,我们在分类列表视图中创建一个滚动条。我们将orientation 属性绑定到isPortrait 属性,并绑定到Qt::Orientation 枚举类型的Horizontal 值以在纵向模式下显示水平滚动条,绑定到Vertical 值以在横向模式下显示垂直滚动条:
ScrollBar {
id: listScrollBar
orientation: window.isPortrait ? Qt.Horizontal : Qt.Vertical
height: window.isPortrait ? 8 : categories.height;
width: window.isPortrait ? categories.width : 8
scrollArea: categories;
anchors.right: categories.right
}与categories 列表视图一样,我们根据isPortrait 属性调整滚动条的宽度和高度。
我们使用scrollArea 属性在categories 列表视图中显示滚动条。
我们使用anchors.right 属性将滚动条锚定在分类列表的右侧。
ScrollBar {
scrollArea: list
width: 8
anchors.right: window.right
anchors.top: window.isPortrait ? categories.bottom : window.top
anchors.bottom: window.bottom
}其次,我们在新闻条目列表视图中创建另一个滚动条。我们希望无论屏幕方向如何,视图右侧都显示一个垂直滚动条,因此可以将width 属性设置为8 ,并将anchors.right 属性绑定到window.right 属性。 我们使用anchors.top 属性,在纵向模式下将滚动条顶部锚定在分类列表底部,在横向模式下锚定在新闻条目列表顶部。我们使用anchors.bottom 属性,在两种模式下均将滚动条底部锚定在列表视图底部。
我们在ScrollBar.qml 中定义了ScrollBar 类型。我们使用带有自定义属性的Item 类型来创建滚动条的容器:
我们使用BorderImage 类型,根据position() 函数计算出的x和y坐标位置来显示滚动条拇指:
BorderImage {
source: "images/scrollbar.png"
border { left: 1; right: 1; top: 1; bottom: 1 }
x: container.orientation == Qt.Vertical ? 2 : container.position()
y: container.orientation == Qt.Vertical ? container.position() : 2
width: container.orientation == Qt.Vertical ? container.width - 4 : container.size()
height: container.orientation == Qt.Vertical ? container.size() : container.height - 4
}我们使用size 函数,根据屏幕方向计算滚动条控点的宽度和高度。
当用户移动滚动区域时,我们使用states 使滚动条可见:
states: State {
name: "visible"
when: container.orientation == Qt.Vertical ?
container.scrollArea.movingVertically :
container.scrollArea.movingHorizontally
PropertyChanges { container { opacity: 1.0 } }
}当状态从"visible" 切换回默认状态时,我们使用transitions 向opacity 属性应用NumberAnimation :
transitions: Transition {
from: "visible"; to: ""
NumberAnimation { properties: "opacity"; duration: 600 }
}
}创建页脚
在Main.qml 中,我们使用Component 类型并将其Rectangle 属性设置为 ,以此为新闻列表视图创建页脚:
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window
import QtQml.XmlListModel
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
onLoadingChanged: {
if (feedModel.status == XmlListModel.Ready)
list.positionViewAtBeginning()
}
RssFeeds { id: rssFeeds }
XmlListModel {
id: feedModel
source: "https://" + window.currentFeed
query: "/rss/channel/item"
XmlListModelRole { name: "title"; elementName: "title"; attributeName: ""}我们将页脚的width 绑定到组件的宽度,并将height 绑定到关闭按钮的高度,以便在未显示任何新闻条目时对齐它们。
创建按钮
在Main.qml 中,我们使用Image 类型创建一个简单的按压按钮,用户点击该按钮即可关闭应用:
XmlListModelRole { name: "content"; elementName: "content"; attributeName: "url" }
XmlListModelRole { name: "link"; elementName: "link"; attributeName: "" }
XmlListModelRole { name: "pubDate"; elementName: "pubDate"; attributeName: "" }
}
ListView {
id: categories
property int itemWidth: 190
width: window.isPortrait ? parent.width : itemWidth
height: window.isPortrait ? itemWidth : parent.height
orientation: window.isPortrait ? ListView.Horizontal : ListView.Vertical
anchors.top: parent.top
model: rssFeeds
delegate: CategoryDelegate {
itemSize: categories.itemWidth
isLoading: window.loading
onClicked: function () {
if (window.currentFeed == feed)
feedModel.reload()
else
window.currentFeed = feed
}
}
spacing: 3
}
ScrollBar {
id: listScrollBar
orientation: window.isPortrait ? Qt.Horizontal : Qt.Vertical
height: window.isPortrait ? 8 : categories.height;
width: window.isPortrait ? categories.width : 8
scrollArea: categories;
anchors.right: categories.right
}
ListView {
id: list
anchors.left: window.isPortrait ? window.left : categories.right
anchors.right: closeButton.left
anchors.top: window.isPortrait ? categories.bottom : window.top
anchors.bottom: window.bottom
anchors.leftMargin: 30
anchors.rightMargin: 4
clip: window.isPortrait
model: feedModel
footer: footerText
delegate: NewsDelegate {}
}
ScrollBar {
scrollArea: list
width: 8
anchors.right: window.right
anchors.top: window.isPortrait ? categories.bottom : window.top
anchors.bottom: window.bottom
}
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
}
}
}
Image {
id: closeButton
source: "content/images/btn_close.png"
scale: 0.8
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 4
opacity: (window.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 应用程序。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.