Qt Quick Demo - RSS ニュース

XmlListModelXmlListModelRole カスタム QML タイプを使って XML データをダウンロードし、ListModelListElement を使ってカテゴリリストを作成し、ListView を使ってデータを表示する QML RSS ニュースリーダーです。

RSS Newsは、以下のQt Quick の特徴を示しています:

  • カスタム QML タイプの使用
  • リストモデルとリスト要素を使ってデータを表現する
  • XML データをダウンロードするための XML リストモデルの使用
  • リストビューを使ってデータを表示する
  • Component 型を使用して、ニュースアイテムリストビューのフッターを作成する。
  • Image 型を使用して、アプリを閉じるためのボタンを作成する。

サンプルを実行する

Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Example を参照してください。

カスタムタイプの使用

RSS Newsアプリでは、それぞれ別の.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の型と同様に定義されますが、プロパティの代わりにロールの定義が含まれます。ロールはデータへのアクセス方法を定義するものであり、データそのものを含むものでもあります。

各リスト要素では、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 が現在の項目の場合、selectedtrue になるように指定します。

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 プロパティを使って、フォント・サイズとテキスト・フォーマットを調整します。

colorscale プロパティを使ってテキストを明るくし、リストアイテムがカレントアイテムの時に少し大きく拡大します。プロパティに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 では、ListView 要素のデータ・ソースとしてXmlListModel 型を使用し、選択されたカテゴリのニュース・アイテムを表示しています:

    XmlListModel {
        id: feedModel

        source: "https://" + window.currentFeed
        query: "/rss/channel/item"

source プロパティとwindow.currentFeed カスタム・プロパティを使用して、選択したカテゴリのニュース・アイテムを取得します。

query プロパティは、XmlListModel が XML ドキュメント内の各<item> に対してモデル項目を生成することを指定します。

モデル項目の属性を指定するには、XmlListModelRole タイプを使用します。各モデル項目は、XML文書内の対応する<item> の値と一致するtitlecontentlinkpubDate 属性を持ちます:

        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: "" }
    }

データを表示するためにListView 型のfeedModel モデルを使います:

    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 {
        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(/&#39;/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 attached プロパティとメイン・ウィンドウのloading プロパティにバインドして、カテゴリ・リスト・アイテムが現在のアイテムであり、XML データがロードされているときにインジケータ画像を表示します。

BusyIndicator.qmlBusyIndicator 型を定義します。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 enum 型の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
    }

categories リストビューと同様に、isPortrait プロパティに基づいてスクロールバーの幅と高さを調整します。

categories リストビューにスクロールバーを表示するには、scrollArea プロパティを使用します。

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.qmlScrollBar タイプを定義します。カスタムプロパティを持つItem タイプを使用して、スクロールバーのコンテナを作成します:

Item {
    id: container

    property variant scrollArea
    property int orientation: Qt.Vertical

    opacity: 0

position() 関数を使用して計算した x と y の位置にスクロールバーの親指を表示するためにBorderImage 型を使用します:

    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 では、Component タイプとRectangle タイプを使用して、ニュース・リスト・ビュー用のフッターを作成しています:

    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() 関数を呼び出します。

サンプルプロジェクト @ code.qt.io

QMLアプリケーションも参照してください

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。