QML范例详解

研究了一段时间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 = op

    if (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 = 0
    display.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"
    }
}


你可能感兴趣的:(Qt,QuickQML,跨平台-QT)