A QML RSS news reader that uses XmlListModel and XmlListModelRole custom QML types to download XML data, ListModel and ListElement to create a category list, and ListView to display the data.
RSS News 演示以下 Qt Quick 特征:
要运行范例从 Qt Creator ,打开 欢迎 模式,然后选择范例从 范例 。更多信息,拜访 构建和运行范例 .
In the RSS News app, we use the following custom types that are each defined in a separate .qml file:
BusyIndicator.qml
CategoryDelegate.qml
NewsDelegate.qml
RssFeeds.qml
ScrollBar.qml
To use the custom types, we add an import statement to the main QML file, rssnews.qml that imports the folder called
content
where the types are located:
import "./content"
在
rssnews.qml
,使用
Rectangle
type with custom properties to create the app main window:
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
We will use the custom properties later for loading XML data and for adjusting the screen layout depending on its orientation.
在
rssnews.qml
,使用
RssFeeds
custom type that we specify in
RssFeeds.qml
to create a list of feed categories:
RssFeeds { id: rssFeeds }
在
RssFeeds.qml
,使用
ListModel
类型采用
ListElement
type to create a category list where list elements represent feed categories:
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" } }
List elements are defined like other QML types except that they contain a collection of role definitions instead of properties. Roles both define how the data is accessed and include the data itself.
For each list element, we use the
名称
role to specify the category name, the
feed
role to specify the URL to load the data from, and the
image
role to display an image for the category.
在
rssnews.qml
,使用
ListView
type to display the category list:
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 }
To lay out the category list horizontally at the top of the window in portrait orientation and vertically on the left side in landscape orientation, we use the
orientation
property. Based on the orientation, we bind either the width or the height of the list to a fixed value (
itemWidth
).
使用
anchors.top
property to position the list view at the top of the screen in both orientations.
使用
model
property to load XML data from the
rssFeeds
model, and
CategoryDelegate
as the delegate to instantiate each item in the list.
在
CategoryDelegate.qml
,使用
Rectangle
type with custom properties to create list elements:
Rectangle { id: delegate property bool selected: ListView.isCurrentItem
We set the
selected
property to the
ListView.isCurrentItem
attached property to specify that
selected
is
true
if
delegate
is the current item.
使用
Image
type
source
property to display the image, centered in the delegate, specified for the list element by the
image
role in the
rssFeeds
list model:
Image { anchors.centerIn: parent source: image }
使用 Text type to add titles to list elements:
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
property to position the title at the top of the list element, with a 20-pixel margin. We use
font
properties to adjust font size and text formatting.
使用
color
and
scale
properties to brighten the text and to scale it slightly larger when the list item is the current item. By applying a
Behavior
to the property, we animate the actions of selecting and deselecting list items.
使用 MouseArea type to download XML data when users tap a category list element:
MouseArea { anchors.fill: delegate onClicked: { delegate.ListView.view.currentIndex = index if (window.currentFeed == feed) feedModel.reload() else window.currentFeed = feed } }
The
anchors.fill
property is set to
delegate
to enable users to tap anywhere within the list element.
使用
onClicked
signal handler to load the XML data for the category list. If the tapped category is already current, the
reload()
function is called to reload the data.
在
rssnews.qml
, we use an
XmlListModel
type as a data source for
ListView
elements to display news items in the selected category:
XmlListModel { id: feedModel source: "https://" + window.currentFeed query: "/rss/channel/item"
使用
source
特性和
window.currentFeed
custom property to fetch news items for the selected category.
The
query
property specifies that the
XmlListModel
generates a model item for each
<item>
在 XML 文档。
使用
XmlListModelRole
type to specify the model item attributes. Each model item has the
title
,
content
,
link
,和
pubDate
attributes that match the values of the corresponding
<item>
in the XML document:
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
model in a
ListView
type to display the data:
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 {} }
To list the news items below the category list in portrait orientation and to its right in landscape orientation, we use the
isPortrait
custom property to anchor the top of the news items list to the left of
window
and bottom of
categories
in portrait orientation and to the right of
categories
and bottom of
window
in landscape orientation.
使用
anchors.bottom
property to anchor the bottom of the list view to the bottom of the window in both orientations.
In portrait orientation, we clip the painting of the news items to the bounding rectangle of the list view to avoid graphical artifacts when news items are scrolled over other items. In landscape, this is not required, because the list spans the entire screen vertically.
使用
model
property to load XML data from the
feedModel
model, and use
NewsDelegate
as the delegate to instantiate each item in the list.
在
NewsDelegate.qml
,使用
Column
type to lay out the XML data:
Column { id: delegate width: delegate.ListView.view.width spacing: 8
Within the column, we use a Row and another column to position images and title text:
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 } }
We generate a textual representation of how long ago the item was posted using the
timeSinceEvent()
JavaScript function:
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
signal handler to open the URL in an external browser when users select the link.
在
CategoryDelegate.qml
,使用
BusyIndicator
custom type to indicate activity while the XML data is being loaded:
BusyIndicator { scale: 0.8 visible: delegate.ListView.isCurrentItem && window.loading anchors.centerIn: parent }
使用
scale
property to reduce the indicator size to
0.8
. We bind the
visible
property to the
isCurrentItem
attached property of the
delegate
list view and
loading
property of the main window to display the indicator image when a category list item is the current item and XML data is being loaded.
定义
BusyIndicator
类型在
BusyIndicator.qml
. We use an
Image
type to display an image and apply a
NumberAnimation
to its
rotation
property to rotate the image in an infinite loop:
Image { id: container source: "images/busy.png"; NumberAnimation on rotation { running: container.visible from: 0; to: 360; loops: Animation.Infinite; duration: 1200 } }
在 APP 中,还可以使用 BusyIndicator 类型从 Qt Quick Controls 模块。
在
rssnews.qml
, we use our own custom
ScrollBar
type to create scroll bars in the category and news item list views. In your apps, you can also use the
ScrollView
类型从
Qt Quick Controls
模块。
First, we create a scroll bar in the category list view. We bind the
orientation
property to the
isPortrait
property and to the
Horizontal
value of the
Qt::Orientation
enum type to display a horizontal scroll bar in portrait orientation and to the
Vertical
value to display a vertical scroll bar in landscape orientation:
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 }
Same as with the
categories
list view, we adjust the width and height of the scroll bar based on the
isPortrait
特性。
使用
scrollArea
property to display the scroll bar in the
categories
list view.
使用
anchors.right
property to anchor the scroll bar to the right side of the category list.
ScrollBar { scrollArea: list width: 8 anchors.right: window.right anchors.top: isPortrait ? categories.bottom : window.top anchors.bottom: window.bottom }
Second, we create another scroll bar in the news item list view. We want a vertical scroll bar to appear on the right side of the view regardless of screen orientation, so we can set the
width
特性到
8
并绑定
anchors.right
property to the
window.right
property. We use the
anchors.top
property to anchor the scroll bar top to the bottom of the category list in portrait orientation and to the top of the news item list in landscape orientation. We use the
anchors.bottom
property to anchor the scroll bar bottom to the list view bottom in both orientations.
定义
ScrollBar
类型在
ScrollBar.qml
. We use an
Item
type with custom properties to create a container for the scroll bar:
Item { id: container property variant scrollArea property int orientation: Qt.Vertical opacity: 0
使用
BorderImage
type to display the scroll bar thumb at the x and y position that we calculate by using the
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
function to calculate the thumb width and height depending on the screen orientation.
使用
状态
to make the scroll bar visible when the users move the scroll area:
states: State { name: "visible" when: container.orientation == Qt.Vertical ? scrollArea.movingVertically : scrollArea.movingHorizontally PropertyChanges { target: container; opacity: 1.0 } }
使用
过渡
以应用
NumberAnimation
到
opacity
property when the state changes from
"visible"
to the default state:
transitions: Transition { from: "visible"; to: "" NumberAnimation { properties: "opacity"; duration: 600 } } }
在
rssnews.qml
,使用
Component
类型采用
Rectangle
type to create a footer for the news list view:
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 } } }
We bind the
width
of the footer to the width of the component and the
height
to the height of close button to align them when no news items are displayed.
在
rssnews.qml
, we use an
Image
type to create a simple push button that users can tap to close the app:
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
to position the close button in the top right corner of the news list view, with 4-pixel margins. Because the close button overlaps the category list in portrait orientation, we animate the
opacity
property to make the button almost fully transparent when users are scrolling the category list.
使用
onClicked
signal handler within a
MouseArea
to call the
quit()
function when users select the close button.
另请参阅 QML 应用程序 .