创建的汽车仪表板。我们将使用动画来展示时钟更新其值的方式的真实感,打开仪表板项目并导航到 main.qml 文件。 找到 Needle 对象的声明,该对象负责可视化车辆的速度。 将以下声明添加到对象中:
main.cpp
#include <QtWidgets>
#include <QQuickView>
#include <QQmlEngine>
#include <QQmlContext>
#include <QtQml>
#include "ui_form.h"
#include "carinfoengine.h"
#include "carinfoproxy.h"
int main(int argc, char **argv) {
QApplication app(argc, argv);
QQmlApplicationEngine engine;
//这些修改为我们的场景创建了一个 QML 引擎,将 CarInfo 实例导出到 QML 引擎的全局上下文,并从位于资源中的文件加载和显示场景。
//重要的是首先导出所有对象,然后才加载场景。这是因为我们希望在初始化场景时所有名称都已可解析,以便可以立即使用它们。如果我们颠倒调用顺序,我们会在控制台上收到许多关于身份未定义的警告。
QString msg = QStringLiteral("Objects of type CarInfoEngine cannot be created");
qmlRegisterUncreatableType<CarInfoEngine>("CarInfo", 1, 0, "CarInfoEngine", msg);
qmlRegisterType<CarInfoProxy>("CarInfo", 1, 0, "CarInfo");
// qmlRegisterType<CarInfo>("CarInfo", 1, 0, "CarInfo");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
carinfo.h
#ifndef CARINFO_H
#define CARINFO_H
#include <QObject>
#include <QWidget>
#include <QDate>
namespace Ui {
class Form;
}
class CarInfoEngine;
class CarInfo : public QWidget {
//这些属性是只读的,并且 NOTIFY 子句定义了在相应属性值更改时发出的信号。 继续为每个属性实现适当的功能。 除了 getter,还可以将 setter 实现为公共插槽。 这是控制汽车速度的属性的示例:
Q_OBJECT
Q_PROPERTY(int speed READ speed WRITE setSpeed NOTIFY speedChanged)
Q_PROPERTY(double distance READ distance WRITE setDistance NOTIFY distanceChanged)
Q_PROPERTY(CarInfoEngine* engine READ engine NOTIFY engineChanged)
public:
CarInfo();
int speed() const;
void setSpeed(int s);
double distance() const;
void setDistance(double d);
CarInfoEngine* engine() const;
signals:
void speedChanged(int);
void distanceChanged(double);
void engineChanged();
private:
CarInfoEngine *m_engine;
Ui::Form *ui;
};
#endif // CARINFO_H
carinfo.cpp
#include "carinfo.h"
#include "ui_form.h"
#include "carinfoengine.h"
CarInfo::CarInfo() : QWidget(0), ui(new Ui::Form) {
//在widget中建立适当的信号槽连接,以便为给定参数修改任何widget或使用设置器槽直接更新该参数的所有widgets。
//您需要向构造函数添加 connect() 语句,以确保从 ui 表单传播信号
ui->setupUi(this);
m_engine = new CarInfoEngine(this);
m_engine->setGear(ui->gearBox->value());
m_engine->setRpm(ui->rpmBox->value());
connect(ui->speedBox, SIGNAL(valueChanged(int)),
this, SIGNAL(speedChanged(int)));
connect(ui->distanceBox, SIGNAL(valueChanged(double)),
this, SIGNAL(distanceChanged(double)));
connect(ui->distanceSlider, &QSlider::valueChanged,
ui->distanceBox, &QDoubleSpinBox::setValue);
connect(this, &CarInfo::distanceChanged,
ui->distanceSlider, &QSlider::setValue);
connect(ui->gearBox, SIGNAL(valueChanged(int)),
m_engine, SLOT(setGear(int)));
connect(ui->rpmBox, SIGNAL(valueChanged(int)),
m_engine, SLOT(setRpm(int)));
connect(m_engine, SIGNAL(gearChanged(int)),
ui->gearBox, SLOT(setValue(int)));
connect(m_engine, SIGNAL(rpmChanged(int)),
ui->rpmBox, SLOT(setValue(int)));
}
int CarInfo::speed() const {
return ui->speedBox->value();
}
void CarInfo::setSpeed(int s) {
ui->speedBox->setValue(s);
}
double CarInfo::distance() const {
return ui->distanceBox->value();
}
void CarInfo::setDistance(double d) {
ui->distanceBox->setValue(d);
}
CarInfoEngine *CarInfo::engine() const {
return m_engine;
}
由于我们要使用小部件来调整属性值,因此请使用表单编辑器为其设计用户界面。 它看起来像这样:
import QtQuick 2.9
import QtQuick.Window 2.3
import CarInfo 1.0
Window {
visible: true
width: backgroundImage.width
height: backgroundImage.height
//我们在这里所做的是将图像添加到窗口并创建三个项目作为仪表板不同元素的容器。
Image {
id: backgroundImage
source: "qrc:/dashboard_1080x720.png"
CarInfo {
id: carData
visible: true
}
Item {
id: leftContainer
property real value: carData.engine.rpm/1000
//容器都在父项中居中,我们使用 horizontalCenterOffset 属性将两个外部项目横向移动
//偏移量和宽度的值基于背景图像的几何形状
//如果您不使用我们的文件,而是使用 Qt Quick 项目自己创建三个部分,则容器将简单地锚定到三个黑色项目的中心。
anchors.centerIn: parent
anchors.horizontalCenterOffset: -350
width: 300
height: 300
//这是一个函数的实现,用于计算沿圆周的位置,基于圆的半径和应放置item的角度
function calculatePosition(angle, radius) {
//该函数将度数转换为弧度并返回所需的点。 该函数期望 width 属性可用,以帮助计算圆心,如果没有给出半径,则用作计算其可行值的方法。
if(radius === undefined) {
radius = width / 2 * 0.8;
}
var a = angle * Math.PI / 180;
var px = width / 2 + radius * Math.cos(a);
var py = width / 2 + radius * Math.sin(a);
return Qt.point(px, py);
}
//有了这样的功能,我们可以使用已经熟悉的 Repeater 元素将项目放置在我们想要的位置。 让我们把这个函数放在 middleContainer 中并声明汽车速度的刻度盘
Repeater {
model: 7
Item {
property point pt: leftContainer.calculatePosition(120+index*35)
x: pt.x
y: pt.y
Label {
anchors.centerIn: parent
text: index
}
}
}
Text {
anchors.centerIn: parent
anchors.verticalCenterOffset: 40
text: "RPM\n[x1000]"
horizontalAlignment: Text.AlignHCenter
color: "#aaa"
font.pixelSize: 16
}
Item {
id: gearContainer
anchors.centerIn: parent
anchors.horizontalCenterOffset: 10
anchors.verticalCenterOffset: -10
Text {
id: gear
property int value: carData.engine.gear
property var gears: [
"R", "N", "1<sup>st</sup>", "2<sup>nd</sup>",
"3<sup>rd</sup>", "4<sup>th</sup>", "5<sup>th</sup>"
]
anchors.left: parent.left
anchors.bottom: parent.bottom
text: gears[value + 1]
color: "yellow"
font.pixelSize: 32
textFormat: Text.RichText
}
}
Needle {
anchors.centerIn: parent
length: parent.width * 0.35
rotation: 120 + 90 + (leftContainer.value * 35)
//Behavior 元素不是立即接受新值
//而是拦截请求并启动 SmoothedAnimation 类以逐渐达到请求的值
Behavior on rotation {
//SmoothedAnimation 类是一种动画类型
//,可以为数字属性设置动画。
//动画的速度不是由它的持续时间决定的
//设置了速度属性。 此属性指示更改值的速度
//但是,动画使用的是非线性路径——它开始缓慢,然后加速到给定的速度,并在动画接近尾声时以平滑的方式减速
SmoothedAnimation {
velocity: 100
}
}
}
}
Item {
id: middleContainer
property int value: carData.speed
anchors.centerIn: parent
width: 400
height: width
function calculatePosition(angle, radius) {
if(radius === undefined) {
radius = width / 2 * 0.8;
}
var a = angle * Math.PI / 180;
var px = width / 2 + radius * Math.cos(a);
var py = width / 2 + radius * Math.sin(a);
return Qt.point(px, py);
}
Repeater {
model: 24 / 2
//表盘由一个repeater组成,可创建 12 个元素。每个元素都是一个使用前面描述的函数定位的项目。该项目有一个标签锚定在它上面,显示给定的速度。我们使用 120 + index * 12 * 2 作为角度表达式,因为我们希望“0”定位在 120 度处,并且接下来的每个项目都进一步定位 24 度。
Item {
property point pt:
middleContainer.calculatePosition(120 + index * 12 * 2)
x: pt.x; y: pt.y
Label {
anchors.centerIn: parent
text: index * 20
}
}
}
Repeater {
model: 120 - 4
Item {
property point pt: middleContainer.calculatePosition(
120 + index * 1.2 * 2, middleContainer.width * 0.35)
x: pt.x
y: pt.y
Rectangle {
width: 2
height: index % 5 ? 5 : 10
color: "white"
rotation: 90 + 120 + index * 1.2 * 2
anchors.centerIn: parent
antialiasing: true
}
}
}
Text {
anchors.centerIn: parent
anchors.verticalCenterOffset: 40
text: "SPEED\n[kph]"
horizontalAlignment: Text.AlignHCenter
color: "#aaa"
font.pixelSize: 16
}
Needle {
anchors.centerIn: parent
length: parent.width * 0.35
size: 4
rotation: 210 + (middleContainer.value * 1.2)
color: "yellow"
Behavior on rotation {
SmoothedAnimation {
velocity: 50
}
}
}
}
Item {
id: rightContainer
anchors.centerIn: parent
anchors.horizontalCenterOffset: 425
width: 150
height: 150
Rectangle {
id: digitalDisplay
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
border.color: "white"
border.width: 1
radius: 10
width: 250
height: 200
color: "transparent"
Item {
anchors.fill: parent
anchors.margins: 10
Label {
id: dateLabel
text: Qt.formatDate(new Date)
anchors.horizontalCenter: parent.horizontalCenter
}
Grid {
id: topGrid
anchors.top: dateLabel.bottom
anchors.topMargin: 15
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: bottomGrid.top
anchors.bottomMargin: 15
columns: 2
spacing: 5
Label {
text: "Temperature:"
width: parent.width/2
}
Label {
text: "+17°C"
horizontalAlignment: Text.AlignRight
width: parent.width / 2 - 5
}
Label {
text: "Humidity:"
width: parent.width / 2
}
Label {
text: "68.4%"
horizontalAlignment: Text.AlignRight
width: parent.width / 2 - 5
}
}
Grid {
id: bottomGrid
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 15
Label {
text: "Distance:"
width: parent.width / 2
}
Label {
text: carData.distance+" km"
horizontalAlignment: Text.AlignRight
width: parent.width / 2 - 5
}
}
}
}
Row {
anchors.top: digitalDisplay.bottom
anchors.topMargin: 10
anchors.left: digitalDisplay.left
anchors.right: digitalDisplay.right
spacing: 10
Label {
text: "ABS"
color: "#222"
}
Label {
text: "ESP"
color: "#da0"
}
Label {
text: "BRK"
color: "#222"
}
Label {
text: "CHECK"
color: "#222"
}
}
}
}
}
Needle.qml
import QtQuick 2.9
Item {
id: root
//该文档定义了一个具有四个属性的项目——针的长度(默认为表盘半径的 80%)
//、针的颜色、middleColor,代表针的固定颜色,以及尺寸,定义如何 针很宽。
property int length: parent.width * 0.4
property color color: "white"
property color middleColor: "red"
property int size: 2
//Item本身没有任何尺寸,仅作为视觉元素的锚点——针本身是一个垂直方向的细长矩形,从末端固定 20 个单位。
Rectangle { //needle
width: root.size
height: length + 20
color: root.color
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: -20
antialiasing: true
}
//固定是一个与针颜色相同的圆圈,中间有一个较小的圆圈,使用不同的填充颜色。 内圆的较小半径是通过从每侧填充外圆 25% 的余量来获得的。
Rectangle { //fixing
anchors.centerIn: parent
width: 8 + root.size
height: width
radius: width / 2
color: root.color
Rectangle { //middle dot
anchors {
fill: parent
margins: parent.width * 0.25
}
color: root.middleColor
}
}
}