Qt简洁速度仪表盘

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.运行效果展示

运行效果如下图所示


speedPanel.gif

4.小知识分享

1.QPainter在创建对象时,必须指定绘制的窗体,否则系统会报QPainter:: Painter not active
2.在使用QPainter绘制时最好保证save()与restore()函数成对出现,否则系统会报警告(大概率不影响绘制)
3.窗体刷新时,update()与repaint()函数的区别是:前者会将重绘事件抛出到事件循环队列等待被检出时绘制,后者则是直接开始当前绘制
4.使用无背景窗体+绘制可以让你的窗体是异形窗体哦

有什么问题可以在线讨论,觉得有用帮忙点个赞吧!^ _ ^

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,046评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,877评论 0 2
  • 年纪越大,人的反应就越迟钝,脑子就越不好使,计划稍有变化,就容易手忙脚乱,乱了方寸。 “玩坏了”也是如此,不但会乱...
    玩坏了阅读 2,135评论 2 1
  • 感动 我在你的眼里的样子,就是你的样子。 相互内化 没有绝对的善恶 有因必有果 当你以自己的价值观幸福感去要求其他...
    周粥粥叭阅读 1,636评论 1 5
  • 昨天考过了阿里规范,心里舒坦了好多,敲代码也犹如神助。早早完成工作回家喽
    常亚星阅读 3,037评论 0 1