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

来源:互联网 发布:贴吧营销软件 编辑:程序博客网 时间:2024/04/29 17:15

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


  


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


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


GenericPodcastApp.qml

import QtQuick 2.0import Ubuntu.Components 0.1import QtQuick.XmlListModel 2.0import Ubuntu.Components.ListItems 0.1 as ListItemimport QtMultimedia 5.0PageStack {    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.0import 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


0 0
原创粉丝点击