研究了一段時間QML,現在對Qt中的一個計算器范例的代碼進行分析,并總結一下前面學習的內容.Qt這種語言大多數還是被用于嵌入式設備上,而QML則是專為嵌入式設備而生的.Qt在桌面開發上占據的比例很小,而且已被Nokia出售,未來的前景如何誰也不好說.但Qt確實很棒,祝福一下吧,如果以后Qt支持Android和蘋果的開發了,在繼續深入研究.
上圖是運行效果圖,界面風格確實很漂亮.鼠標點擊按鈕后還有一個變灰的反應,整體來說界面簡潔大氣.而且實現了計算器的基本功能,這里要說明的是,所有功能都是由QML獨立完成的,沒有任何qt插件參與.而且調整界面的尺寸后,還會使界面發生旋轉.這樣的功能這樣的界面效果要是使用Qt或Delphi,VC來實現的話,相信還是有一點的工作量的.正如前面翻譯的文章中所說的那樣,QML適合于界面上有大量簡單動態元素的情形.像這種計算器程序或時鐘程序使用QML實現就太方便了.
在總結一下以前翻譯的幾篇文章中的要點:QML中的核心是屬性綁定,對象的屬性發生了變化不一定就一定有函數在給屬性賦值.可能是其他的屬性與其有綁定關系,當這些屬性發生變化時,QML引擎會自動為屬性重新計算值.動畫效果的實現依靠State和Transition.閑話少說,直接分析代碼吧.
計算器程序的組織結構
在core目錄中,定義了按鈕組件Button和顯示計算器輸入信息及計算結果的Display組件.core/images目錄中是按鈕的圖片和Display組件的背景圖片.
還有一個qmldir文件,這個文件沒有后綴.其中存儲了目錄中組件的名稱和位置.
按鈕組件
先來看看Button.qml文件的定義.這個文件定義了按鈕組件,為了分析方便,我將原碼直接拷貝過來,每行代碼后面加上注釋.
import QtQuick 1.0 //導入QML基本元素 版本號為1.0
BorderImage { //聲明一個BorderImage元素 BorderImage一般用來作為邊界圖像.這里直接用來顯示按鈕圖像 id: button //設置其唯一標識
PRoperty alias Operation: buttonText.text //定義一個屬性別名供外部使用,當給operation賦值或讀取operation時,實際上在操作buttonText.text的值 buttonText元素在后面定義property string color: "" //定義字符串屬性color,默認值為""signal clicked //定義一個信號,這里的信號和Qt中的信號概念上相同,用法上也一致//source屬性指定其圖片的地址,注意這里使用了屬性綁定,最終的值與color有關,//如果color的值發生了變化,source的值自動變化.最終計算的source值正好是上圖中幾個按鈕的背景圖片的名稱source: "images/button-" + color + ".png"; clip: true border { left: 10; top: 10; right: 10; bottom: 10 } //設置邊界 定義了圖像距離外邊框的距離 這里上下左右都空閑10個像素Rectangle { //聲明了一個矩形,這個矩形在鼠標點擊的時候設置opacity為0.4,使按鈕變灰.但不會影響按鈕上顯示的文字,因為文字是在其下方聲明的. id: shade //設置唯一標示 anchors.fill: button; /*完全平鋪到button上*/radius: 10;/*定義圓角半徑*/ color: "black"; opacity: 0/*定義了透明度,0為完全透明,1為完全不透明*/}Text { //聲明按鈕上的文本 id: buttonText //設置唯一標識 上面定義屬性property alias operation: buttonText.text就引用了這個標識.Text上顯示的文本就是text屬性的值 anchors.centerIn: parent;/*居中顯示*/ anchors.verticalCenterOffset: -1/*垂直居中偏移-1像素*/ font.pixelSize: parent.width > parent.height ? parent.height * .5 : parent.width * .5 //計算字體大小,為按鈕寬高最小值的一半 style: Text.Sunken;/*設置文本風格*/ color: "white"; styleColor: "black"; smooth: true}MouseArea { //設置鼠標響應區域 id: mouseArea anchors.fill: parent //整個按鈕區域都可響應鼠標 onClicked: { doOp(operation) //定義doOp函數,注意doOp在calculator.qml中定義,這個qml引用了Button.qml,由于qml是聲明式的,因此可先引用后聲明(定義). button.clicked() //觸發button的click信號 }}states: State { //定義State實現動畫效果 這個State實現當mouseArea是鼠標按下狀態時,修改shade的屬性opacity的值為0.4,也就是當按鈕被按下時看到一層淡淡的灰色. name: "pressed"; when: mouseArea.pressed == true //when關鍵字定義狀態觸發的條件 PropertyChanges { target: shade; opacity: .4 } //改變shade的opacity屬性}}
Display組件
import QtQuick 1.0 //導入1.0版本的QtQuick模塊
BorderImage { //定義顯示背景圖片元素 id: image //唯一標識
property alias text : displayText.text //屬性別名 設置text就是給displayText.text賦值property alias currentOperation : operationText //屬性別名 這是一個Text元素類型的屬性source: "images/display.png" //設備背景圖片border { left: 10; top: 10; right: 10; bottom: 10 } //設置圖片與邊框的距離Text { id: displayText anchors { //定位 right: parent.right;/*右側與父對象的右側對齊*/ verticalCenter: parent.verticalCenter;/*垂直居中*/ verticalCenterOffset: -1/*垂直偏移量-1 顯示稍偏下*/ rightMargin: 6; /*右邊界間隔6個像素*/left: operationText.right/*左側與operationText的右側對齊*/ } font.pixelSize: parent.height * .6; text: "0"; horizontalAlignment: Text.AlignRight; elide: Text.ElideRight color: "#343434"; smooth: true; font.bold: true}Text { id: operationText font.bold: true;/*粗體*/ font.pixelSize: parent.height * .7 color: "#343434"; smooth: true anchors { left: parent.left;/*靠左顯示*/ leftMargin: 6;/*左側邊距6像素*/ verticalCenterOffset: -3; verticalCenter: parent.verticalCenter }}}
Display組件定義了一個背景圖,上面有兩個Text,這兩個Text一個靠左,一個靠右,平鋪在Display組件上,而且兩個Text直接具有描點關系:anchors{displayText.left: operationText.right}.displayText的左側總與operationText的右側相連.說明在改變大小時operationText不變,而displayText是可伸展的.
calculator定義
兩個共用組件介紹完了,現在看看calculator.qml.這是計時器的定義文件.
import QtQuick 1.0 import “Core” //導入Core目錄中定義的組件 引擎查找目錄中的qmldir文件(無后綴),根據其中的內容導入定義的組件. import “Core/calculator.js” as CalcEngine //導入javaScript文件內容 也可作為一個組件來看,并定義了組件別名,下面使用文件中定義的函數時可用:別名.方法名
Rectangle { id: window
width: 360; height: 480 //定義窗口尺寸color: "#282828"property string rotateLeft: "/u2939"property string rotateRight: "/u2935"property string leftArrow: "/u2190"property string division : "/u00f7"property string multiplication : "/u00d7"property string squareRoot : "/u221a"property string plusminus : "/u00b1"function doOp(operation) { CalcEngine.doOperation(operation) } //定義了個函數,供下面調用.這個函數又調用了js文件中的doOperation函數,注意參數operation是按鈕上的文字內容.Item { id: main state: "orientation " + runtime.orientation //runtime.orienttation返回界面的顯示方向. 如果方向改變,就會重新設置state的值,其屬性也會按state定義的相應更改. property bool landscapeWindow: window.width > window.height property real baseWidth: landscapeWindow ? window.height : window.width //取寬高中最小的那個值 property real baseHeight: landscapeWindow ? window.width : window.height //取寬高中最大的那個值 property real rotationDelta: landscapeWindow ? -90 : 0 rotation: rotationDelta //根據窗口寬與高的大小來調整旋轉角度,只用一行代碼搞定界面旋轉 width: main.baseWidth height: main.baseHeight anchors.centerIn: parent //定義一個Column元素,單列排布其中的子元素.上面是Display 下面是多個按鈕的區域 Column { id: box; spacing: 8 anchors { fill: parent; topMargin: 6; bottomMargin: 6; leftMargin: 6; rightMargin: 6 } //顯示Display組件 Display { id: display width: box.width-3 height: 64 } //定義按鈕區域 應使用Column元素聲明 其中的子元素垂直分布 共分三個區域按鈕 界面中紫色,綠色,及下面的其他按鈕三個部分 Column { id: column; spacing: 6 property real h: ((box.height - 72) / 6) - ((spacing * (6 - 1)) / 6)//計算出每個按鈕的高度 property real w: (box.width / 4) - ((spacing * (4 - 1)) / 4) //計算出每個按鈕的寬度 //定義紫色按鈕區域 按鈕之所以顯示為紫色,因為Button的color屬性設置為purple,在Button按鈕組件定義中,其背景圖片的source屬性與color綁定,確定了顯示哪個圖片 Row { //Row元素定義一行,其中包含的元素水平布局 spacing: 6 Button { width: column.w; height: column.h; color: 'purple'; operation: "Off" } Button { width: column.w; height: column.h; color: 'purple'; operation: leftArrow } Button { width: column.w; height: column.h; color: 'purple'; operation: "C" } Button { width: column.w; height: column.h; color: 'purple'; operation: "AC" } } //定義綠色按鈕區域 Row { spacing: 6 property real w: (box.width / 4) - ((spacing * (4 - 1)) / 4) Button { width: column.w; height: column.h; color: 'green'; operation: "mc" } Button { width: column.w; height: column.h; color: 'green'; operation: "m+" } Button { width: column.w; height: column.h; color: 'green'; operation: "m-" } Button { width: column.w; height: column.h; color: 'green'; operation: "mr" } } //定義其他按鈕 Grid { //Grid元素定義一個網格,其中的元素都占據一個小格 id: grid; rows: 5;/*指定網格的行數*/ columns: 5;/*指定網格的列數*/ spacing: 6 property real w: (box.width / columns) - ((spacing * (columns - 1)) / columns) Button { width: grid.w; height: column.h; operation: "7"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "8"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "9"; color: 'blue' } Button { width: grid.w; height: column.h; operation: division } Button { width: grid.w; height: column.h; operation: squareRoot } Button { width: grid.w; height: column.h; operation: "4"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "5"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "6"; color: 'blue' } Button { width: grid.w; height: column.h; operation: multiplication } Button { width: grid.w; height: column.h; operation: "x^2" } Button { width: grid.w; height: column.h; operation: "1"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "2"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "3"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "-" } Button { width: grid.w; height: column.h; operation: "1/x" } Button { width: grid.w; height: column.h; operation: "0"; color: 'blue' } Button { width: grid.w; height: column.h; operation: "." } Button { width: grid.w; height: column.h; operation: plusminus } Button { width: grid.w; height: column.h; operation: "+" } Button { width: grid.w; height: column.h; operation: "="; color: 'red' } } } } //定義狀態,main元素的state屬性指定為如下狀態名稱時,其屬性值就會發生改變 通常為了具有動畫效果,states要與transitions配合使用 states: [ State { name: "orientation " + Orientation.Landscape PropertyChanges { target: main; rotation: 90 + rotationDelta; width: main.baseHeight; height: main.baseWidth } }, State { name: "orientation " + Orientation.PortraitInverted PropertyChanges { target: main; rotation: 180 + rotationDelta; } }, State { name: "orientation " + Orientation.LandscapeInverted PropertyChanges { target: main; rotation: 270 + rotationDelta; width: main.baseHeight; height: main.baseWidth } } ] //定義動畫效果 transitions: Transition { SequentialAnimation { //定義一個順序執行的動畫 RotationAnimation { direction: RotationAnimation.Shortest; duration: 300; easing.type: Easing.InOutQuint } //旋轉動畫效果屬性 NumberAnimation { properties: "x,y,width,height"; duration: 300; easing.type: Easing.InOutQuint } //在x,y,width,height屬性發生變化時的動畫屬性 } }}}
算法
計時器的算法定義在一個單獨的Javascript文件中.
var curVal = 0 var memory = 0 var lastOp = “” var timer = 0
function disabled(op) { if (op == “.” && display.text.toString().search(/./) != -1) { return true } else if (op == squareRoot && display.text.toString().search(/-/) != -1) { return true } else { return false } }
function doOperation(op) { if (disabled(op)) { return }
if (op.toString().length==1 && ((op >= "0" && op <= "9") || op==".") ) { if (display.text.toString().length >= 14) return; // No arbitrary length numbers if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp == ".") ) { display.text = display.text + op.toString() } else { display.text = op } lastOp = op return}lastOp = opif (display.currentOperation.text == "+") { //已經按下了+號 display.text = Number(display.text.valueOf()) + Number(curVal.valueOf())} else if (display.currentOperation.text == "-") { display.text = Number(curVal) - Number(display.text.valueOf())} else if (display.currentOperation.text == multiplication) { display.text = Number(curVal) * Number(display.text.valueOf())} else if (display.currentOperation.text == division) { display.text = Number(Number(curVal) / Number(display.text.valueOf())).toString()//開始計算} else if (display.currentOperation.text == "=") {}if (op == "+" || op == "-" || op == multiplication || op == division) { display.currentOperation.text = op curVal = display.text.valueOf() return}curVal = 0display.currentOperation.text = ""if (op == "1/x") { display.text = (1 / display.text.valueOf()).toString()} else if (op == "x^2") { display.text = (display.text.valueOf() * display.text.valueOf()).toString()} else if (op == "Abs") { display.text = (Math.abs(display.text.valueOf())).toString()} else if (op == "Int") { display.text = (Math.floor(display.text.valueOf())).toString()} else if (op == plusminus) { display.text = (display.text.valueOf() * -1).toString()} else if (op == squareRoot) { display.text = (Math.sqrt(display.text.valueOf())).toString()} else if (op == "mc") { memory = 0;} else if (op == "m+") { memory += display.text.valueOf()} else if (op == "mr") { display.text = memory.toString()} else if (op == "m-") { memory = display.text.valueOf()} else if (op == leftArrow) { display.text = display.text.toString().slice(0, -1) if (display.text.length == 0) { display.text = "0" }} else if (op == "Off") { Qt.quit();} else if (op == "C") { display.text = "0"} else if (op == "AC") { curVal = 0 memory = 0 lastOp = "" display.text ="0"}}
新聞熱點
疑難解答