1.Qt简洁速度仪表盘
QPainter类可以在窗体小部件和其他绘制设备上执行低级的绘制。
QPainter提供了高度优化的功能来完成大多数图形用户界面程序所需要的绘图。可以画任何东西,从简单的线条到复杂的形状,如馅饼和和弦。它还可以绘制对齐的文本和像素地图。通常情况下,它在一个“自然”的坐标系中绘制,但它也可以进行视图和窗口的转换。QPainter可以对继承QPaintDevice类的任何对象进行操作。
1.1需要自绘的场景
贴图满足不了动态需求,一般在有需要动态图的情况下需要自绘来满足需求。
1.2自定义绘制基础知识
一般地,在自定义绘制中,所有的绘制操作都是在painterEvent函数中执行,因为所有的页面更新(包括自动更新和手动更新)都会发生paintEvent()事件,因此,在paintEvent中执行绘制操作都可以完成页面的重绘,达到页面更新的目的。
1.3自定义简洁仪表盘简单介绍
本文使用QPainter绘制出一个简单的仪表盘仅供参考,需要注意在绘制仪表盘过程中,新建QPainter对象时,必须指定绘图设备,否则报Painter not active警告,导致绘制不成功。
2.核心部分代码
2.1绘制之前初始化QPainter对象
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event) //防止编译以报警告,形参未使用
QPainter Paint(this); //指定绘图设备为当前窗体
Paint.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);//设置绘制时使能抗锯齿,文字和绘制都是
Paint.setBrush(QColor("#414141")); //设置绘制画刷
Paint.drawRect(this->rect());
//给窗体设置背景,要使用this->rect,必须是在变换坐标之前,变换之后,系统原点会发生变化
Paint.translate(width()/2,height()/2); //设置绘制的原点在窗体的中心,不设置原点默认是在窗体的左上角
Paint.setPen(Qt::NoPen); //不使用画笔笔,使用画刷,没有轮廓线比较美观
//调用绘制背景
paintBackground(Paint);
//调用绘制内圆
paintInsideCircle(Paint);
//调用刻度
paintTickMarks(Paint);
if(isShow)
paintNeedle(Paint);
//绘制遮罩
paintMaskWidget(Paint);
}
2.2绘制表盘背景
绘制时保证save()与restore()成对出现,save()之后的代码可以对画家的属性进行设置,绘制完成后restore()之后设置的属性解绑。此处drawEllipse()指定了绘制圆盘的左上角坐标和右下角坐标。
void Widget::paintBackground(QPainter &Paint)
{
Paint.save();
int radius = (qMin(width(),height())/2)*0.9;
Paint.setBrush(QColor("#505050")); //设置画刷的颜色,画背景
Paint.drawEllipse(-radius,-radius,radius*2,radius*2);
Paint.restore();
}
2.3绘制刻度与时速文字
绘制刻度线时,分别绘制了大刻度,小刻度,与时速文字,一般常规的有两种绘制方式,一种是使用三角函数的方式绘制,一种是使用旋转坐标系的方式绘制,下边分别展示了两种方法。
void Widget::paintTickMarks(QPainter &Paint)
{
Paint.save();
int radius = (qMin(width(),height())/2)-60;
int r = radius*0.85; //这里可以计算,也可以使用半径的百分比
Paint.setFont(QFont("Ubuntu", 17));
Paint.setPen(QPen(QColor(255,255,255)));
QFontMetricsF fm = QFontMetricsF(Paint.font()); //QFontMetricsF类提供字体度量信息,可以提供字体的外接矩形相关的信息
int Angle = 60;
int gap = (360-Angle*2) / 10; //10代表准备规划十个梯度,Angle代表偏角
for(int i=0; i<=10; i+=1)
{
int angle = 90+Angle+gap*i; //角度,10格子画一个刻度值
float angleArc =( angle % 360) * 3.14 / 180; //转换为弧度
int x = (r)*cos(angleArc);
int y = (r)*sin(angleArc);
QString value = QString::number(i*10);
int w = (int)fm.width(value);
int h = (int)fm.height();
x = x - w/2;
y = y + h/4;
Paint.drawText(QPointF(x, y),value);
}
Paint.restore();
//上述使用三角函数完成了表盘数字的绘制,下边使用旋转的方式绘制表刻度
//先绘制大刻度
Paint.save();
int rp = radius*0.65;
Paint.setBrush(QBrush("#FFFFFF"));
Paint.rotate(60);
for(int i= 0;i<=10;i++){
Paint.drawRoundedRect(QRectF(-3,rp,6,30),3,3);
Paint.rotate(24);
}
Paint.restore();
//绘制小刻度
Paint.save();
Paint.setBrush(QBrush("#FFFFFF"));
Paint.rotate(60);
for(int j = 0; j < 50;j++){
Paint.rotate(4.8);
Paint.drawRoundedRect(QRectF(-2,rp,4,22),2,2);
}
Paint.restore();
}
2.4绘制表盘的遮罩
仪表盘上边部分使用了阴影遮罩,采用两个不同半径的偏心圆相减得到的路径去绘制阴影部分,通过设置阴影颜色的不透明度,来达到显示遮罩的目的。
void Widget::paintMaskWidget(QPainter &Paint)
{
Paint.save();
int radius = (qMin(width(),height())/2)*0.9;
Paint.setPen(Qt::NoPen);
QPainterPath smallCircle;
QPainterPath bigCircle;
radius -= 1;
smallCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
radius *= 2;
bigCircle.addEllipse(-radius, -radius + 400, radius * 2, radius * 2);
//高光的形状为小圆扣掉大圆的部分
QPainterPath highlight = smallCircle - bigCircle;
QColor overlayColor = QColor("#F5F5F5");
QLinearGradient linearGradient(0, -radius / 2, 0, 0);
overlayColor.setAlpha(100);
linearGradient.setColorAt(0.0, overlayColor);
overlayColor.setAlpha(30);
linearGradient.setColorAt(1.0, overlayColor);
setBrush(linearGradient);
Paint.rotate(-20);
Paint.drawPath(highlight);
Paint.restore();
}
2.5绘制仪表指针
仪表指针部分绘制一个闭合的三角形,先计算指针的显示位置,记录三角形的三个顶点,然后绘制,由于指针是受外部驱动而偏转,此处使用变量的方式修改指针偏转的角度。
void Widget::paintNeedle(QPainter &Paint)
{
Paint.save();
Paint.setBrush(QColor("#DF6969"));
Paint.rotate(mNumber);
Paint.drawPolygon(point,3);
Paint.restore();
}
指针部分动画
系统中加入了一个显示速度的Label由外而内的飘入,其次是窗体的淡入的动画,设置该部分只是一个小插曲,完全可以去掉,由于Label是飘入,只需要修改Label的pos属性,其次窗体的淡出值需要修改窗体的windowOpacity属性,参见QWidget帮助手册,其中重写了窗体的resizeEvent()。保证Label在窗体变化时总是可以显示在合适的位置。
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowFlag(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_TranslucentBackground,true);
mValue = new QLabel(this);
mValue->setFixedSize(QSize(100,100));
mValue->setStyleSheet("border-radius:50px;background-color:#FAFAFA;color:#000000;");
mValue->setAlignment(Qt::AlignCenter);
mValue->setFont(QFont("ubuntu",12,100));
QPropertyAnimation *opacity = new QPropertyAnimation(this,"windowOpacity");
opacity->setStartValue(0);
opacity->setEndValue(1.0);
opacity->setDuration(1500);
opacity->setEasingCurve(QEasingCurve::Linear);
opacity->start();
QPropertyAnimation *pos = new QPropertyAnimation(mValue,"pos");
pos->setStartValue(QPoint(-100,-100));
pos->setEndValue(QPoint(this->rect().center()-QPoint(mValue->width()/2,mValue->height()/2)));
pos->setDuration(1000);
pos->setEasingCurve(QEasingCurve::Linear);
pos->start();
connect(pos,&QPropertyAnimation::finished,this,[=](){
mValue->setText("0km/h");
isShow=true;update();
mTimer->start(50);
});
int radius = (qMin(width(),height())/2);
int r = radius*0.75;
point[0]=QPointF(-25,0);
point[1]=QPointF(25,0);
point[2]=QPointF(0,190);
mTimer = new QTimer(this);
connect(mTimer,&QTimer::timeout,this,[=](){
mNumber-=10;
if(mNumber < 60){
mTimer->stop();
mNumber = 60;
}
update();
});
QSlider *slider = new QSlider(this);
slider->setOrientation(Qt::Horizontal);
slider->setMinimum(0);
slider->setMaximum(100);
int xWidth = width()-100;
slider->setGeometry(50,this->height()-30,xWidth,30);
connect(slider,&QSlider::valueChanged,this,[=](int value){
mNumber=(value*2.4)+60;
mValue->setText(QString("%1km/h").arg(value));
mCircle = value;
update();
});
}
//重写resizeEvent()事件
void Widget::resizeEvent(QResizeEvent *event)
{
if(mValue){
int side = qMin(this->width(),this->height());
mValue->move(QPoint(this->rect().center()-QPoint(mValue->width()/2,mValue->height()/2)));
}
}
3.运行效果展示
运行效果如下图所示
4.小知识分享
1.QPainter在创建对象时,必须指定绘制的窗体,否则系统会报QPainter:: Painter not active
2.在使用QPainter绘制时最好保证save()与restore()函数成对出现,否则系统会报警告(大概率不影响绘制)
3.窗体刷新时,update()与repaint()函数的区别是:前者会将重绘事件抛出到事件循环队列等待被检出时绘制,后者则是直接开始当前绘制
4.使用无背景窗体+绘制可以让你的窗体是异形窗体哦
有什么问题可以在线讨论,觉得有用帮忙点个赞吧!^ _ ^