创建可以重复利用的QML Component--Podcast播放器代码实例

在我们设计QML应用时,很重要的一点就是设计一个可以被重复利用的软件Component.它可以反复在其它的应用中被利用.这如同在我们的C++及其它语言中设计自己的应用一样,我们可以创建自己的模块.只需要修改很少的部分或甚至不用做任何的修改就可以被其它的应用广泛使用.我们在前两天的文章"利用Ubuntu Component Store来增加我们的QML Components"中已经做过一些介绍了.今天我们通过一个具体的实例来展示是如何做的.在今天的例程中,我们来介绍一个podcast播放器的实例.


  


在我们今天设计的应用中,有两个页面.第一个页面,是一个podcast RSS feed的介绍及其播放audio的列表.在第二个页面中,显示的是它的一个logo的图像及几个可以用于播放的按钮.


这个设计采用了一个叫做GenericPodcastApp.qml的Component.其设计如下:


GenericPodcastApp.qml

import QtQuick 2.0
import Ubuntu.Components 0.1
import QtQuick.XmlListModel 2.0
import Ubuntu.Components.ListItems 0.1 as ListItem
import QtMultimedia 5.0

PageStack {
    id: ps
    Component.onCompleted: ps.push(front)

    property alias squareLogo: logo.source
    property alias author: author.text
    property alias category: category.text
    property alias name: front.title
    property alias description: desc.text
    property alias feed: rssmodel.source

    Action {
        id: reloadAction
        text: "Reload"
        iconName: "reload"
        onTriggered: rssmodel.reload()
    }

    Page {
        id: front
        visible: true

        tools: ToolbarItems {
            ToolbarButton {
                action: reloadAction
            }
        }

        Flickable {
            anchors.fill: parent
            contentHeight: row.height + desc.height + showlist.height + desc.anchors.topMargin + showlist.anchors.topMargin

            Row {
                id: row
                width: parent.width
                anchors.top: parent.top
                anchors.left: parent.left
                anchors.topMargin: units.gu(1)
                anchors.leftMargin: units.gu(1)
                anchors.rightMargin: units.gu(1)
                spacing: units.gu(2)

                UbuntuShape {
                    id: logoshape
                    width: parent.width / 3
                    height: parent.width / 3
                    image: Image {
                        id: logo
                        fillMode: Image.PreserveAspectFit
                    }
                    ActivityIndicator {
                        running: logo.status != Image.Ready
                        anchors.centerIn: logoshape
                    }
                }

                Column {
                    width: row.width - row.spacing - row.anchors.leftMargin- row.anchors.rightMargin - logoshape.width
                    spacing: units.gu(1)
                    anchors.bottom: parent.bottom
                    Label {
                        id: author
                        fontSize: "small"
                        wrapMode: Text.WordWrap
                        width: parent.width
                    }
                    Label {
                        id: category
                        wrapMode: Text.WordWrap
                        width: parent.width
                        fontSize: "small"
                    }
                }
            }

            Label {
                id: desc
                anchors.top: row.bottom
                anchors.left: parent.left
                anchors.topMargin: units.gu(2)
                anchors.leftMargin: row.anchors.leftMargin
                width: parent.width - (row.anchors.leftMargin * 2)
                wrapMode: Text.WrapAtWordBoundaryOrAnywhere
                property bool expanded: false
                clip: true
                height: {
                    if (desc.contentHeight > units.gu(12) && !expanded) {
                        return units.gu(12)
                    }
                    return desc.contentHeight
                }

                Rectangle {
                    color: "black"
                    width: moretxt.contentWidth + units.gu(2)
                    height: moretxt.contentHeight
                    anchors.bottom: desc.bottom
                    anchors.right: desc.right
                    Label {
                        id: moretxt
                        color: "white"
                        anchors.centerIn: parent
                        text: desc.expanded ? "<<" : ">>"
                    }
                    visible: desc.contentHeight > units.gu(12)
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: desc.expanded = !desc.expanded
                }
            }

            Column {
                id: showlist
                anchors.top: desc.bottom
                anchors.topMargin: units.gu(2)
                width: parent.width
                Repeater {
                    model: rssmodel
                    ListItem.Standard {
                        text: title
                        width: parent.width
                        progression: true
                        onClicked: { ps.push(episode, {download: model.download, summary: model.summary, title: model.title}); }
                    }
                }
            }
            ActivityIndicator {
                anchors.top: desc.bottom
                anchors.topMargin: units.gu(2)
                height: reloadbutton.height
                width: height
                anchors.horizontalCenter: parent.horizontalCenter
                running: rssmodel.status != XmlListModel.Ready && rssmodel.status != XmlListModel.Error
            }
        }
    }

    Page {
        id: episode
        property string download
        property string summary
        visible: false

        Flickable {
            anchors.fill: parent
            contentHeight: biglogo.height + positionbar.height + buttons.height + epdesc.height + (epcol.spacing * 4)

            Column {
                id: epcol
                width: parent.width
                spacing: units.gu(2)

                Image {
                    id: biglogo
                    source: logo.source
                    width: parent.width
                    height: parent.width
                    fillMode: Image.PreserveAspectFit
                }

                Rectangle {
                    id: positionbar
                    width: buttons.width
                    anchors.horizontalCenter: parent.horizontalCenter
                    height: units.gu(5)
                    color: "transparent"

                    Rectangle {
                        id: actualbar
                        width: parent.width
                        height: units.gu(0.5)
                        color: "#999999"
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.centerIn: parent
                    }
                    Rectangle {
                        width: units.gu(0.5)
                        height: units.gu(2)
                        color: "#444444"
                        anchors.verticalCenter: actualbar.verticalCenter
                        x: actualbar.width * aud.position / aud.duration
                    }
                    MouseArea {
                        anchors.fill: parent
                        onPressed: {
                            aud.seek(aud.duration * mouse.x / actualbar.width)
                        }
                    }
                }

                Row {
                    id: buttons
                    spacing: units.gu(2)
                    anchors.horizontalCenter: parent.horizontalCenter
                    Button {
                        text: "<<30"
                        onClicked: aud.seek(aud.position - 30000)
                    }
                    Button {
                        text: aud.status == Audio.Loading ? "load" : (aud.playbackState == Audio.PlayingState ? "Stop" : "Play")
                        onClicked: {
                            aud.source = episode.download;
                            if (aud.playbackState == Audio.PlayingState) {
                                aud.pause();
                            } else {
                                aud.play();
                            }
                            console.log(aud.duration, aud.position);
                        }
                    }
                    Button {
                        text: "30>>"
                        onClicked: aud.seek(aud.position + 30000)
                    }

                }

                Label {
                    id: epdesc
                    width: parent.width - units.gu(4)
                    anchors.horizontalCenter: parent.horizontalCenter
                    text: episode.summary
                    wrapMode: Text.Wrap
                    color: "white"
                    textFormat: Text.RichText
                }
            }
        }
    }

    XmlListModel {
        id: rssmodel
        query: "/rss/channel/item"
        namespaceDeclarations: "declare namespace itunes='http://www.itunes.com/dtds/podcast-1.0.dtd'; declare namespace content='http://purl.org/rss/1.0/modules/content/';"
        XmlRole { name: "title"; query: "title/string()" }
        XmlRole { name: "pubDate"; query: "pubDate/string()" }
        XmlRole { name: "download"; query: "enclosure/@url/string()" }
        XmlRole { name: "summary"; query: "content:encoded/string()" }
    }

    Audio {
        id: aud
    }
}



在上面的设计中非常简单.我们通过在Component中设计一些property,在使用这个Component时只需要在外面对他们进行赋值即可.就像我以前在有些教程中讲到的.如果我们需要修改到这个Component中的其中的子Component的属性的话,我们可以通过QML中的alias来实现.第一个页面的最上面显示的是一个podcast的最基本的信息.在它的最下面,它使用了一个repeater来显示所有的epsode列表.


            Column {
                id: showlist
                anchors.top: desc.bottom
                anchors.topMargin: units.gu(2)
                width: parent.width
                Repeater {
                    model: rssmodel
                    ListItem.Standard {
                        text: title
                        width: parent.width
                        progression: true
                        onClicked: { ps.push(episode, {download: model.download, summary: model.summary, title: model.title}); }
                    }
                }
            }


在这里我们使用了rssmodel,它的定义如下:


    XmlListModel {
        id: rssmodel
        query: "/rss/channel/item"
        namespaceDeclarations: "declare namespace itunes='http://www.itunes.com/dtds/podcast-1.0.dtd'; declare namespace content='http://purl.org/rss/1.0/modules/content/';"
        XmlRole { name: "title"; query: "title/string()" }
        XmlRole { name: "pubDate"; query: "pubDate/string()" }
        XmlRole { name: "download"; query: "enclosure/@url/string()" }
        XmlRole { name: "summary"; query: "content:encoded/string()" }
    }

更多关于 XmlListModel的使用,可以参阅 API介绍.在我的博客中也有很多的介绍.大家可以参阅我的一些设计的例程.


到目前为止,我们已经设计好我的GenericPodcastApp Component.如果在其它的地方需要应用它的话,我们只需要这么做:


Main.qml


import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "podcast.liu-xiao-guo"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(60)
    height: units.gu(85)

    GenericPodcastApp {
        name: "Bad Voltage"
        squareLogo: "images/logo.jpg"
        author: "Stuart Langridge, Jono Bacon, Jeremy Garcia, and Bryan Lunduke"
        category: "Technology"
        feed: "http://www.badvoltage.org/feed/ogg/"
        description: "Every two weeks Bad Voltage delivers an amusing take on technology, Open Source, politics, music, and anything else we think is interesting."
    }
}


它的应用非常地简单.我们只需要对它里面的properties进行赋值即可.我们很容地把它修个成为其它的源的podcast.当然我们也可以利用这个设计更加复杂的应用,这样,我们可以把我们想要的rss feed轻易地加入到我们的podcast列表中.这个练习就交给我们的开发者吧.


整个项目的源码在:https://github.com/liu-xiao-guo/podcast


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值