qml绘制仪表盘控件

刚开始上手qt,说实话迷得很啊。。。

不过qt做的界面是真的漂亮,之前在b站看qt官方发布的一些视频,仪表盘煞是好看

今天倒腾了一天,用qml绘制了一个简单的汽车仪表控件,趁现在还热着,先记下来

新建一个空的qml工程

创建工程的时候要注意,工程路径中不能有中文,否则会导致编译失败

创建完成后按例编译运行,确保工程能正常编译,否则等写得差不多却发现无法正常编译那就很尴尬了。。。

再添加一个单独的.qml文件,我们在里面编写自定义的控件,这样在别的工程中只要载入这个qml文件就能使用了~~

起个名字 (Mycar。。。哈哈,暂且就这样吧

暂时就以这个为例子吧,今天做出来的也就和这个像

从上面这张图中,我们可以看到这个表盘主要是由几个弧构成。qt官方这个例子是先通过Photoshop设计出表盘整体的样式,然后通过插件直接从ps中导出.qml,接着就是在qt软件上进行编辑。不得不说这种设计和逻辑分开的方式的确非常的棒!让天堂的归天堂,让尘土的归尘土。ps咱不熟悉,过qt的ps插件倒是挺有意思,具体的我还没有研究明白,等哪天搞懂了再写篇文章记一下。需要这个插件的朋友请往:https://code.qt.io/cgit/qt-labs/photoshop-qmlexporter.git/ 或者同性社区: https://github.com/qt-labs/photoshop-qmlexporter

因为这个仪表主要由弧构成,因此我们需要在canvas中对仪表的各个部分进行绘制。首先,我们需要绘制两个重叠的圆弧。最下面的圆弧设置成浅灰色作为背景,最顶上的圆弧则实时显示我们汽车的速度。刚刚创建的qml文件中,键入如下代码:

// file - Mycar.qml

import QtQuick 2.0

Item {
    id: carItem
    width: 100
    height: 100

    // 背景圆弧线宽
    property int btm_lineWidth: 15
    // 背景圆弧颜色
    property color btm_backgroundColor: Qt.rgba(0, 0, 0, 0.1);
    // 背景圆弧半径 开始角度 结束角度
    property int btm_r: 20
    property double btm_startAngle: 0
    property double btm_endAngle: 90

    onBtm_lineWidthChanged: canvas.requestPaint()
    onBtm_backgroundColorChanged: canvas.requestPaint()
    onBtm_rChanged: canvas.requestPaint()
    onBtm_startAngleChanged: canvas.requestPaint()
    onBtm_endAngleChanged: canvas.requestPaint()

    // 顶层圆弧线宽
    property int top_lineWidth: 10
    // 顶层圆弧颜色
    property color top_backgroundColor: "lightgreen"
    // 顶层圆弧半径 开始角度 结束角度
    property int top_r: 20
    property double top_startAngle: 0
    property double top_endAngle: 90

    onTop_lineWidthChanged: canvas.requestPaint()
    onTop_backgroundColorChanged: canvas.requestPaint()
    onTop_rChanged: canvas.requestPaint()
    onTop_startAngleChanged: canvas.requestPaint()
    onTop_endAngleChanged: canvas.requestPaint()

    Canvas {
        id: canvas
        width: carItem.width
        height: carItem.height

        onPaint: {
            var ctx = getContext("2d");
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 画背景圆弧
            ctx.lineWidth = carItem.btm_lineWidth;
            ctx.strokeStyle = carItem.btm_backgroundColor;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.btm_r, (carItem.btm_startAngle/180*Math.PI), (carItem.btm_endAngle/180*Math.PI));
            ctx.stroke();


            // 画顶层圆弧
            ctx.lineWidth = carItem.top_lineWidth;
            ctx.strokeStyle = carItem.top_backgroundColor;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.top_r, (carItem.top_startAngle/180*Math.PI), (carItem.top_endAngle/180*Math.PI));
            ctx.stroke();
        }
    }
}

这里啰嗦一下,我顶层圆弧和底层圆弧属性的命名前置分别是“top_”、“btm_”。为了在设计模式中更改这些属性的值时,图像能够马上更新,代码中使用了形似 onXxxxxxChanged(格式:on<Property>Changed)的信号处理器,当某个属性的值变更时,会触发canvas的重绘(即,canvas.requestPaint())。举个栗子,例如代码中的btm_lineWidth变量,写法就是:

on + Btm_lineWidth + Changed     ,     即:onBtm_lineWidthChanged: canvas.requestPaint()

如果开头的b没有大写,就会报:

后面在main.qml中引用Mycar.qml的控件也是如此,调用的对象名称首字母必须大写。详见qt文档:https://doc.qt.io/qt-5/qtqml-documents-definetypes.html

接着,回到main.qml文件中,引用我们刚刚写的仪表控件

点击左侧的 设计,可以看到自定义的仪表控件已经被载入到界面中。右下角的则是我们在Mycar.qml中定义的,我们可以通过修改这些达到更改顶层和底层圆弧的大小、颜色等目的

接下来,调整一下仪表的位置和大小,这里我还增加了一些控件,一个开关用来控制顶层圆弧的线宽,滑块用来模拟汽车的速度,文本框用来显示速度。。。

在main.qml中给Mycar一个id=speed_car,然后将顶层圆弧的终止角度与滑块进行绑定。当然你可以使用下图的方式进行绑定,也可以直接在Mycar中进行书写。这里我们约定这个速度仪表盘的最大刻度为200Km/h,将0~200Km/h映射到我们的仪表盘上。

点击左下角的运行,滑动滑块,可以看到开关控制的两种效果

不过作为仪表盘来说,虽然现在有了点模样,但还缺少了刻度盘。qt官方的刻度盘是ps设计好的,之后再在qt中把速度的值用文本框加上,这样就能设置字体和样式了。这里咱没有ps,不过没关系啊,用代码画出来就行,撸起袖子就是干

回到Mycar.qml文件中,将绘制刻度盘的代码加上

import QtQuick 2.0

Item {
    id: carItem
    width: 100
    height: 100

    // 背景圆弧线宽
    property int btm_lineWidth: 15
    // 背景圆弧颜色
    property color btm_backgroundColor: Qt.rgba(0, 0, 0, 0.1);
    // 背景圆弧半径 开始角度 结束角度
    property int btm_r: 20
    property double btm_startAngle: 0
    property double btm_endAngle: 90

    onBtm_lineWidthChanged: canvas.requestPaint()
    onBtm_backgroundColorChanged: canvas.requestPaint()
    onBtm_rChanged: canvas.requestPaint()
    onBtm_startAngleChanged: canvas.requestPaint()
    onBtm_endAngleChanged: canvas.requestPaint()

    // 顶层圆弧线宽
    property int top_lineWidth: 10
    // 顶层圆弧颜色
    property color top_backgroundColor: "lightgreen"
    // 顶层圆弧半径 开始角度 结束角度
    property int top_r: 20
    property double top_startAngle: 0
    property double top_endAngle: 90

    onTop_lineWidthChanged: canvas.requestPaint()
    onTop_backgroundColorChanged: canvas.requestPaint()
    onTop_rChanged: canvas.requestPaint()
    onTop_startAngleChanged: canvas.requestPaint()
    onTop_endAngleChanged: canvas.requestPaint()

    // 刻度盘
    property color dial_color: "#000000"
    property int dial_lineWidth: 3
    property int dial_addR: 2          // 通过调整该变量可以控制刻度盘圆弧与底层圆弧的距离
    property int dial_longNum: 5       // 刻度盘长刻度线的数量
    property int dial_longLen: 10      // 刻度盘长刻度线的长度

    onDial_colorChanged: canvas.requestPaint()
    onDial_lineWidthChanged: canvas.requestPaint()
    onDial_addRChanged: canvas.requestPaint()
    onDial_longNumChanged: canvas.requestPaint()
    onDial_longLenChanged: canvas.requestPaint()

    Canvas {
        id: canvas
        width: carItem.width
        height: carItem.height

        onPaint: {
            var ctx = getContext("2d");
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 画背景圆弧
            ctx.lineWidth = carItem.btm_lineWidth;
            ctx.strokeStyle = carItem.btm_backgroundColor;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.btm_r, (carItem.btm_startAngle/180*Math.PI), (carItem.btm_endAngle/180*Math.PI));
            ctx.stroke();

            // 画大刻度盘
            ctx.lineWidth = carItem.dial_lineWidth;
            ctx.strokeStyle = carItem.dial_color;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.btm_r+carItem.btm_lineWidth+carItem.dial_addR, (carItem.btm_startAngle/180*Math.PI), (carItem.btm_endAngle/180*Math.PI));
            var tmp_step = (carItem.btm_endAngle-carItem.btm_startAngle)/carItem.dial_longNum;
            for(var i=carItem.btm_startAngle;i<carItem.btm_endAngle+tmp_step;i+=tmp_step) {
                var tmp_x = (carItem.width/2)+(carItem.btm_r+carItem.btm_lineWidth+carItem.dial_addR)*Math.cos(i/180*Math.PI);
                var tmp_y = (carItem.width/2)+(carItem.btm_r+carItem.btm_lineWidth+carItem.dial_addR)*Math.sin(i/180*Math.PI);
                ctx.moveTo(tmp_x, tmp_y);
                // 绘制长刻度线
                ctx.lineTo(tmp_x+carItem.dial_longLen*Math.cos(i/180*Math.PI), tmp_y+(carItem.dial_longLen*Math.sin(i/180*Math.PI)));
            }
            ctx.stroke();

            // 画顶层圆弧
            ctx.lineWidth = carItem.top_lineWidth;
            ctx.strokeStyle = carItem.top_backgroundColor;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.top_r, (carItem.top_startAngle/180*Math.PI), (carItem.top_endAngle/180*Math.PI));
            ctx.stroke();
        }
    }
}

调整刻度盘的参数,嘿嘿,还是有点模样的

接着用标签控件将速度刻度加上

运行效果:

工程完整代码如下:

main.qml

// file - main.qml

import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello qt")

    Mycar {
        id: speed_car
        x: 175
        y: 93
        width: 291
        height: 238
        dial_addR: -6
        dial_longNum: 10
        dial_longLen: 15
        dial_lineWidth: 3
        btm_lineWidth: 22
        top_lineWidth: 10
        top_endAngle: slider.value*1.3+140
        top_startAngle: 140
        btm_endAngle: 400
        btm_startAngle: 140
        btm_r: 120
        top_r: 120

        Text {
            id: speed
            x: 104
            y: 116
            width: 89
            height: 44
            text: slider.value
            style: Text.Normal
            font.weight: Font.ExtraBold
            font.capitalization: Font.MixedCase
            font.pixelSize: 40
            font.bold: true
            font.family: "Verdana"
            horizontalAlignment: Text.AlignHCenter
                }

        Label {
            id: speed_label
            x: 131
            y: 154
            width: 45
            height: 30
            text: qsTr("Km/h")
            font.pointSize: 11
            font.bold: true
            verticalAlignment: Text.AlignBottom
        }

        Label {
            id: label1
            x: 8
            y: 235
            width: 23
            height: 25
            text: qsTr("0")
            font.weight: Font.Normal
            horizontalAlignment: Text.AlignHCenter
            font.pointSize: 14
        }

        Label {
            id: label2
            x: 263
            y: 235
            width: 33
            height: 25
            text: qsTr("200")
            horizontalAlignment: Text.AlignHCenter
            font.pointSize: 14
            font.weight: Font.Normal
        }

        Label {
            id: label3
            x: -28
            y: 172
            width: 23
            height: 25
            text: qsTr("20")
            horizontalAlignment: Text.AlignHCenter
            font.pointSize: 14
            font.weight: Font.Normal
        }
    }

    Switch {
        id: sth
        x: 501
        y: 10
        text: "Wifi"

        onClicked: {
            if(sth.position) {
                speed_car.top_lineWidth = speed_car.btm_lineWidth;
            } else {
                speed_car.top_lineWidth = 10
            }
        }
    }

    Slider {
        id: slider
        x: 220
        y: 367
        font.pointSize: 14
        stepSize: 1
        to: 200
        from: 0
        value: 0

        onValueChanged: {
            if(value<60) {
                speed.color = "black"
            }
            else if(value<120) {
                speed.color = "#f2ac28"
            }
            else {
                speed.color = "red"
            }
            speed_label.color = speed.color
        }
    }

    Label {
        id: label4
        x: 466
        y: 264
        width: 42
        height: 25
        text: qsTr("180")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label5
        x: 146
        y: 192
        width: 23
        height: 25
        text: qsTr("40")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label6
        x: 476
        y: 192
        width: 23
        height: 25
        text: qsTr("160")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label7
        x: 172
        y: 123
        width: 23
        height: 25
        text: qsTr("60")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label8
        x: 445
        y: 123
        width: 35
        height: 25
        text: qsTr("140")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label9
        x: 236
        y: 73
        width: 23
        height: 25
        text: qsTr("80")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label10
        x: 382
        y: 72
        width: 36
        height: 25
        text: qsTr("120")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

    Label {
        id: label11
        x: 310
        y: 62
        width: 23
        height: 25
        text: qsTr("100")
        horizontalAlignment: Text.AlignHCenter
        font.pointSize: 14
        font.weight: Font.Normal
    }

}

Mycar.qml

// file - Mycar.qml

import QtQuick 2.0

Item {
    id: carItem
    width: 100
    height: 100

    // 背景圆弧线宽
    property int btm_lineWidth: 15
    // 背景圆弧颜色
    property color btm_backgroundColor: Qt.rgba(0, 0, 0, 0.1);
    // 背景圆弧半径 开始角度 结束角度
    property int btm_r: 20
    property double btm_startAngle: 0
    property double btm_endAngle: 90

    onBtm_lineWidthChanged: canvas.requestPaint()
    onBtm_backgroundColorChanged: canvas.requestPaint()
    onBtm_rChanged: canvas.requestPaint()
    onBtm_startAngleChanged: canvas.requestPaint()
    onBtm_endAngleChanged: canvas.requestPaint()

    // 顶层圆弧线宽
    property int top_lineWidth: 10
    // 顶层圆弧颜色
    property color top_backgroundColor: "lightgreen"
    // 顶层圆弧半径 开始角度 结束角度
    property int top_r: 20
    property double top_startAngle: 0
    property double top_endAngle: 90

    onTop_lineWidthChanged: canvas.requestPaint()
    onTop_backgroundColorChanged: canvas.requestPaint()
    onTop_rChanged: canvas.requestPaint()
    onTop_startAngleChanged: canvas.requestPaint()
    onTop_endAngleChanged: canvas.requestPaint()

    // 刻度盘
    property color dial_color: "#000000"
    property int dial_lineWidth: 3
    property int dial_addR: 2          // 通过调整该变量可以控制刻度盘圆弧与底层圆弧的距离
    property int dial_longNum: 5       // 刻度盘长刻度线的数量
    property int dial_longLen: 10      // 刻度盘长刻度线的长度

    onDial_colorChanged: canvas.requestPaint()
    onDial_lineWidthChanged: canvas.requestPaint()
    onDial_addRChanged: canvas.requestPaint()
    onDial_longNumChanged: canvas.requestPaint()
    onDial_longLenChanged: canvas.requestPaint()

    Canvas {
        id: canvas
        width: carItem.width
        height: carItem.height

        onPaint: {
            var ctx = getContext("2d");
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 画背景圆弧
            ctx.lineWidth = carItem.btm_lineWidth;
            ctx.strokeStyle = carItem.btm_backgroundColor;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.btm_r, (carItem.btm_startAngle/180*Math.PI), (carItem.btm_endAngle/180*Math.PI));
            ctx.stroke();

            // 画大刻度盘
            ctx.lineWidth = carItem.dial_lineWidth;
            ctx.strokeStyle = carItem.dial_color;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.btm_r+carItem.btm_lineWidth+carItem.dial_addR, (carItem.btm_startAngle/180*Math.PI), (carItem.btm_endAngle/180*Math.PI));
            var tmp_step = (carItem.btm_endAngle-carItem.btm_startAngle)/carItem.dial_longNum;
            for(var i=carItem.btm_startAngle;i<carItem.btm_endAngle+tmp_step;i+=tmp_step) {
                var tmp_x = (carItem.width/2)+(carItem.btm_r+carItem.btm_lineWidth+carItem.dial_addR)*Math.cos(i/180*Math.PI);
                var tmp_y = (carItem.width/2)+(carItem.btm_r+carItem.btm_lineWidth+carItem.dial_addR)*Math.sin(i/180*Math.PI);
                ctx.moveTo(tmp_x, tmp_y);
                // 绘制长刻度线
                ctx.lineTo(tmp_x+carItem.dial_longLen*Math.cos(i/180*Math.PI), tmp_y+(carItem.dial_longLen*Math.sin(i/180*Math.PI)));
            }
            ctx.stroke();

            // 画顶层圆弧
            ctx.lineWidth = carItem.top_lineWidth;
            ctx.strokeStyle = carItem.top_backgroundColor;
            ctx.beginPath();
            ctx.arc(carItem.width/2, carItem.width/2, carItem.top_r, (carItem.top_startAngle/180*Math.PI), (carItem.top_endAngle/180*Math.PI));
            ctx.stroke();
        }
    }
}

工程包:https://download.csdn.net/download/t01051/12654094

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值