一、效果展示
今儿来分析一篇常见的ui布局,完全使用qml编写,ui交互效果友好,如图1所示,是一个常见的客户端新闻展示效果,左侧是一个列表,右侧是新闻详情。
图1 新闻效果图
二、源码分析
首先先来总体分析下该示例代码的工程目录,如图2所示,总共有6个qml文件。其中BusyIndicator和ScrollBar组件是qml已经存在的组件,NewsDelegate组件是新闻详情页中的一项,CategoryDelegate组件是左侧列表中的一项,RssFeeds组件是左侧新闻列表数据源,rssnews文件是主程序文件。
图2 工程目录
结合图1看程序工程目录,是不是觉着一目了然。NewsDelegate组件和CategoryDelegate组件是两个绘制代理,RssFeeds组件提供一个视图数据源,其中还有一个视图的数据源在rssnews.qml文件内部定义。接下来我主要分析下主程序文件rssnews.qml和NewsDelegate绘制代理
1、主程序文件
主程序文件代码如下,程序中关键的地方都有注释,相比于之前的文章注释少了许多,大多都是一些常见的属性没有了注释。
1 import QtQuick 2.2 2 import QtQuick.XmlListModel 2.0 3 import QtQuick.Window 2.1 4 import "./content" 5 6 Rectangle { 7 id: window 8 9 800 10 height: 480 11 12 property string currentFeed: rssFeeds.get(0).feed//get方法为ListModel内置方法,返回指定索引item 13 property bool loading: feedModel.status === XmlListModel.Loading//是否是加载中。。。 14 property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation 15 16 onLoadingChanged: { 17 if (feedModel.status == XmlListModel.Ready) 18 list.positionViewAtBeginning() 19 } 20 21 RssFeeds { id: rssFeeds } 22 23 XmlListModel { 24 id: feedModel 25 26 source: "http://" + window.currentFeed 27 query: "/rss/channel/item[child::media:content]" 28 namespaceDeclarations: "declare namespace media = 'http://search.yahoo.com/mrss/';" 29 30 XmlRole { name: "title"; query: "title/string()" } 31 // Remove any links from the description 32 XmlRole { name: "description"; query: "fn:replace(description/string(), '<a href=.*/a>', '')" } 33 XmlRole { name: "image"; query: "media:content/@url/string()" } 34 XmlRole { name: "link"; query: "link/string()" } 35 XmlRole { name: "pubDate"; query: "pubDate/string()" } 36 } 37 38 ListView {//左侧新闻列表 39 id: categories 40 property int itemWidth: 190 41 42 isPortrait ? parent.width : itemWidth 43 height: isPortrait ? itemWidth : parent.height 44 orientation: isPortrait ? ListView.Horizontal : ListView.Vertical 45 anchors.top: parent.top 46 model: rssFeeds 47 delegate: CategoryDelegate { itemSize: categories.itemWidth } 48 spacing: 3 49 } 50 51 ScrollBar { 52 id: listScrollBar 53 54 orientation: isPortrait ? Qt.Horizontal : Qt.Vertical 55 height: isPortrait ? 8 : categories.height; 56 isPortrait ? categories.width : 8 57 scrollArea: categories;//关联滚动的区域 58 anchors.right: categories.right//锚点定位 59 } 60 61 ListView {//右侧新闻详情 由多个项NewsDelegate组成 62 id: list 63 64 anchors.left: isPortrait ? window.left : categories.right 65 anchors.right: closeButton.left 66 anchors.top: isPortrait ? categories.bottom : window.top 67 anchors.bottom: window.bottom 68 anchors.leftMargin: 30 69 anchors.rightMargin: 4 70 clip: isPortrait 71 model: feedModel 72 footer: footerText//页脚 视图最底部的修饰 73 delegate: NewsDelegate {} 74 } 75 76 ScrollBar { 77 scrollArea: list 78 8 79 anchors.right: window.right 80 anchors.top: isPortrait ? categories.bottom : window.top 81 anchors.bottom: window.bottom 82 } 83 84 Component { 85 id: footerText 86 87 Rectangle { 88 parent.width 89 height: closeButton.height 90 color: "lightgray" 91 92 Text { 93 text: "RSS Feed from Yahoo News" 94 anchors.centerIn: parent 95 font.pixelSize: 14 96 } 97 } 98 } 99 100 Image {//关闭按钮 点击退出程序 101 id: closeButton 102 source: "content/images/btn_close.png" 103 scale: 0.8 104 anchors.top: parent.top 105 anchors.right: parent.right 106 anchors.margins: 4 107 opacity: (isPortrait && categories.moving) ? 0.2 : 1.0 108 Behavior on opacity { 109 NumberAnimation { duration: 300; easing.type: Easing.OutSine } 110 } 111 112 MouseArea { 113 anchors.fill: parent 114 onClicked: { 115 Qt.quit() 116 } 117 } 118 } 119 }
footer属性指定页脚,就像word文件的页脚一样,位于ListView最低端,如图1中第一帧的RSS Feed from Yahoo News字段,其实每个页面都有这个字段,只是都位于ListView内容最低端。
2、新闻详情页中项
如图1所示,该NewsDelegate绘制代理是右侧页面中的一条新闻项,右侧页面是一个ListView,内部有许多项组成,每一项都是由该代理绘制。
1 //新闻详情中的一条 2 import QtQuick 2.2 3 4 Column { 5 id: delegate 6 delegate.ListView.view.width 7 spacing: 8 8 9 // Returns a string representing how long ago an event occurred 10 function timeSinceEvent(pubDate) { 11 var result = pubDate; 12 13 // We need to modify the pubDate read from the RSS feed 14 // so the JavaScript Date object can interpret it 15 var d = pubDate.replace(',','').split(' '); 16 if (d.length != 6) 17 return result; 18 19 var date = new Date([d[0], d[2], d[1], d[3], d[4], 'GMT' + d[5]].join(' ')); 20 21 if (!isNaN(date.getDate())) { 22 var age = new Date() - date; 23 var minutes = Math.floor(Number(age) / 60000); 24 if (minutes < 1440) { 25 if (minutes < 2) 26 result = qsTr("Just now"); 27 else if (minutes < 60) 28 result = '' + minutes + ' ' + qsTr("minutes ago") 29 else if (minutes < 120) 30 result = qsTr("1 hour ago"); 31 else 32 result = '' + Math.floor(minutes/60) + ' ' + qsTr("hours ago"); 33 } 34 else { 35 result = date.toDateString(); 36 } 37 } 38 return result; 39 } 40 41 Item { height: 8; delegate.width } 42 43 Row { 44 parent.width 45 spacing: 8 46 47 Column { 48 Item {//占位 49 4 50 height: titleText.font.pixelSize / 4 51 } 52 53 Image { 54 id: titleImage 55 source: image//image对应模型中的字段 56 } 57 } 58 59 Text { 60 id: titleText 61 62 text: title//image对应模型中的字段 63 delegate.width - titleImage.width 64 wrapMode: Text.WordWrap 65 font.pixelSize: 26 66 font.bold: true 67 } 68 } 69 70 Text {//距离新闻发布时间+带有link字样的超链接 71 delegate.width 72 font.pixelSize: 12 73 textFormat: Text.RichText 74 font.italic: true 75 text: timeSinceEvent(pubDate) + " (<a href="" + link + "">Link</a>)" 76 onLinkActivated: { 77 Qt.openUrlExternally(link)//link对应模型中的字段 78 } 79 } 80 81 Text { 82 id: descriptionText 83 84 text: description//对应模型中的字段 85 parent.width 86 wrapMode: Text.WordWrap//换行模式 字不能拆分 87 font.pixelSize: 14//字号 88 textFormat: Text.StyledText//支持一些基本的文本样式标记 89 horizontalAlignment: Qt.AlignLeft//水平靠左 90 } 91 }
三、小节
看了有一些qml示例代码,也一直主要在分析qml代码,本小节插播一段个人总结吧,也算是小小感慨下。
不同于以往的QWidget窗口程序,qml写界面非常简洁,从以往的示例中就能感觉的到,在友好交互方面qml比QWidget做的好,比如List下拉到头时的弹簧效果、完美的加载中展示和远程图片加载等等。qml是声明性语言,即不在像C++那样需要编译后才能运行,在代码编写时只需要关注ui,可以根据需要自己封装组件,把需要外界使用的属性使用导出的方式暴露给外界,每一个组件属性都有OnPropertyChanged槽,当属性发生变化时该槽随即执行。
可能是由于一直从事C++相关的工作,没有声明性语言的基础,在阅读qml代码时总是感觉有一种代码散乱无处整理的感觉,现在小小的示例代码亦是如此,等到正真做起项目来不知道会是怎样一番场景。比如说绘制代理在访问一些属性时,直接访问的是模型中的字段,如果字段名称写错,这种错误只能到运行时异常后才能慢慢排查,类似于这样的代码其实有很多,突然之间就跳出一句无厘头的代码,这在C++中根本不可能出现。。。呵呵呵。。。