QT繪製簡易錶盤

1、簡介

最近學習了一下QT,熟悉了一段時間後發現它的功能還是挺強大的,同時也比較方便用戶上手去使用。現在就基於最近學習的內容,實現一個簡易的帶指針旋轉功能的錶盤。文中錶盤的實現是基於QT的QPainter類中的繪圖方法,同時外加QT的定時器設計完成的。效果上肯定沒有貼圖片那麼美觀,不過兩者的設計思想是基本一樣的,這裡的設計方法可以提供給你一個不錯的參考。

2、設計思路

在講解設計思路前還是先來看下繪製的錶盤所實現的效果吧!後面對實現效果進行一步步拆解來講解,這樣或許會更加直觀,更便於理解。

 

 

我們先不去考慮指針旋轉這一動態過程實現,可以將錶盤分解析為3個組成部分,錶盤的外形輪廓、指針和顯示的當前速度的數值。外形輪廓由一個圓弧和一些指示刻度組成,它的繪製肯定要使用QT中的畫圓弧的函數、畫線函數還有顯示文本函數。指針是一個不規則的多邊形,它的繪製會用到QT中的繪製多邊形的函數。顯示當前速度值比較簡單些,直接使用顯示文本函數繪製。

有了靜態部分的基礎,現在開始考慮指針的動態旋轉過程和旋轉過程中的漸變效果是如何實現的。指針旋轉的角度應該和當前的轉速相互對應,當前轉速改變時,會根據新的轉速計算出當前指針位於什麼角度的位置,然後可以調用QT的旋轉角度函數讓多邊形指針旋轉到這個位置。旋轉的漸變效果其實是通過繪製扇形實現的,要繪製扇形的角度和指針旋轉的角度是一樣,由於繪製的扇形的內部的著色採用了顏色的線性內插,所以不同的角度顯示的顏色程度不同,因此給人以漸變的效果。

轉速的周期改變是在定時器中完成的,構造函數中初始化一個周期定時器,當定時器時間超時,根據當前轉速的狀態(上行還是下行),確定讓轉速自增1還是自減1。轉速改變時,調用函數讓介面進行重新繪製。

3、核心函數

所用到的繪製函數都是QPainter類中的方法, QPainter可以理解成是個畫家,這個畫家有很多種繪製圖形的方法,它可以在所以繼承QPaintDevice的類上進行繪製。

繪製多邊形:

  drawPolygon

QT中drawPolygon有很多重載版本,要傳入的參數一般就是要指定繪製多邊形的所有頂點位置。

繪製圓弧:

  drawArc

同理,QT中也有很多drawArc的重載版本。它傳入的參數比較特殊,QT中的繪製圓弧是在一個正方形中操作的,要傳入的是正方形區域的大小,繪製圓弧的圓心默認是在傳入正方形區域中心。除此之外還需要指定要繪製圓弧的起始角度和跨越的角度,它傳入是值是實際角度*16。 當傳入的角度值為正,表示逆時針繪製圓弧,角度值為負,表示順時鐘繪製圓弧。

繪製扇形:

  drawPie

繪製扇形和繪製圓弧的參數類似,可參考繪製圓弧。

QPainter坐標轉換:

  translate

如執行painter.translate(100, 100),後續繪製的參考坐標變成了相對100, 100位置而言的。執行painter.drawLine(0, 0, 0, 20),實際繪製直線的兩端點是(100, 100)和(100, 120)

旋轉:

  ratate

坐標轉換和旋轉過程可參考下圖:

保存和恢復QPainter狀態:

  save

  restore

save保存當前QPainter狀態(即當前對QPainter的設置)到堆棧,restore恢復之前保存的狀態。這兩個方法在QPainter繪圖中很有用,詳細可參考後面程式碼。

4、程式碼實現

先從定時器函數函數來看,當構造函數中設置的定時器超時,會產生定時器事件,timerEvent被自動調用

void Widget::timerEvent(QTimerEvent *e)
{
    int timerId = e->timerId();

    if(this->time_id == timerId) {
        if(this->status == 0) {
            this->speed += 1;
            if(this->speed >= 180)
                this->status = 1;
        }else {
            this->speed -= 1;
            if(this->speed <= 0)
                this->status = 0;

        }

        this->update();
    }
}

判斷當前轉速是處於上行還是下行狀態,根據轉速所處的狀態修改轉速。然後調用this->update(), 該函數會產生Widget類中的重繪事件,paintEvent函數被執行。

painteEvent函數內容如下:

void Widget::paintEvent(QPaintEvent *event)
{
    qreal angle;

    angle = (qreal)270 / (180-1); 

    QPainter painter(this);

    painter.translate(width()/2, height()/2); 

    drawFrame(&painter, angle); //①

    drawPointer(&painter, angle); //②

    drawSpeed(&painter); //③
}

① 繪製儀錶盤圓弧形狀的外框

② 根據轉速大小,計算當前指針所在的角度,然後繪製指針和產生漸變效果的扇形

③ 顯示當前轉速值

drawFrame函數

void Widget::drawFrame(QPainter *painter, qreal angle)
{
    painter->save();
    
    painter->setBrush(QBrush(QColor(0, 255, 0, 255), Qt::SolidPattern));
    painter->drawArc(-200, -200, 400, 400, -135*16, -270*16); //①
    
    painter->restore();
    
    for(int i = 0; i < 180; i++) { //②
       painter->save();

       painter->rotate(-225 + i * angle);

       if(i % 10 == 0) {
           painter->drawLine(180, 0, 200, 0);
       }else {
           painter->drawLine(190, 0, 200, 0);
       }

       painter->restore();
    }

    painter->save();

    for(int i = 0; i < 9; i++) { //③

        int xTextPos = 180 * qCos((225 - i * 15)*3.14/180);
        int yTextPos = -180 * qSin((225 - i * 15)*3.14/180);
        painter->drawText(xTextPos+5, yTextPos+10, QString::number(i * 10));
        painter->drawText(-xTextPos-25, yTextPos+10, QString::number((18 - i) * 10));
    }
    painter->drawText(-10, -165, "90");
    
    painter->restore();
}

① 繪製錶盤的外形圓弧

② 繪製圓弧上的刻度資訊

③ 繪製錶盤外形的刻度旁邊的數值,這裡利用了圓的對稱性

drawPointer函數

void Widget::drawPointer(QPainter *painter, qreal angle)
{
    QPoint point[4] = {
        QPoint(0, 10),
        QPoint(-10, 0),
        QPoint(0, -170),
        QPoint(10, 0),
    };

    painter->save();

    QLinearGradient linear;           //①
    linear.setStart(-200, -200);
    linear.setFinalStop(200, 200);
    linear.setColorAt(0, QColor(0, 255, 255, 0));
    linear.setColorAt(1, QColor(0, 255, 255, 255));
    painter->setPen(Qt::NoPen);
    painter->setBrush(linear);
    painter->drawPie(-200, -200, 400, 400, 225 * 16, -(angle * this->speed) * 16);

    painter->restore();

    painter->save();

    painter->setBrush(QBrush(QColor(0, 0, 0, 255), Qt::SolidPattern));
    painter->rotate(-135 + this->speed * angle);
    painter->drawPolygon(point, 4); //②

    painter->restore();
}

① 根據當前速度計算扇形區域的大小,繪製漸變扇形

② 根據當前速度計算指針所要在的位置,旋轉指針到該位置

drawSpeed函數

void Widget::drawSpeed(QPainter *painter)
{
    painter->save();

    painter->setPen(QColor("#0"));
    //  繪製速度
    QFont font("Times", 10, QFont::Bold);
    font.setBold(true);
    font.setPixelSize(66);
    painter->setFont(font);
    painter->drawText(-60, 100, 120, 92, Qt::AlignCenter, QString::number(speed));
    painter->restore();
}

drawSpeed函數在指定位置,使用指定顏色字體,顯示當前速度

5、小結

到此,模擬儀錶盤的實現講解完畢了,這也算對這幾天QT的學習做了一個小小的總結。從使用過程中可以感受到使用QT進行圖形介面的設計,軟體編寫上還是比較靈活的。QT的GUI功能非常很強大,它提供的介面函數很多,但想要都用好的確很難,平時還需要多看看QT官方文檔中的解釋和提供的demo。

 

完整程式碼

#include "widget.h"

#include <QPainter>
#include <QBrush>
#include <QLabel>
#include <QTimerEvent>
#include <QLinearGradient>
#include <QFont>

#include <QtMath>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    resize(800, 480);
    setWindowTitle("模擬儀錶盤");

    this->speed = 0;
    this->status = 0;

    this->time_id = this->startTimer(50);
}

Widget::~Widget()
{
}

void Widget::paintEvent(QPaintEvent *event)
{
    qreal angle;

    angle = (qreal)270 / (180-1);

    QPainter painter(this);

    painter.translate(width()/2, height()/2);

    drawFrame(&painter, angle);

    drawPointer(&painter, angle);

    drawSpeed(&painter);
}

void Widget::drawFrame(QPainter *painter, qreal angle)
{
    painter->save();
    
    painter->setBrush(QBrush(QColor(0, 255, 0, 255), Qt::SolidPattern));
    painter->drawArc(-200, -200, 400, 400, -135*16, -270*16);
    
    painter->restore();
    
    for(int i = 0; i < 180; i++) {
       painter->save();

       painter->rotate(-225 + i * angle);

       if(i % 10 == 0) {
           painter->drawLine(180, 0, 200, 0);
       }else {
           painter->drawLine(190, 0, 200, 0);
       }

       painter->restore();
    }

    painter->save();

    for(int i = 0; i < 9; i++) {

        int xTextPos = 180 * qCos((225 - i * 15)*3.14/180);
        int yTextPos = -180 * qSin((225 - i * 15)*3.14/180);
        painter->drawText(xTextPos+5, yTextPos+10, QString::number(i * 10));
        painter->drawText(-xTextPos-25, yTextPos+10, QString::number((18 - i) * 10));
    }
    painter->drawText(-10, -165, "90");
    
    painter->restore();
}

void Widget::drawPointer(QPainter *painter, qreal angle)
{
    QPoint point[4] = {
        QPoint(0, 10),
        QPoint(-10, 0),
        QPoint(0, -170),
        QPoint(10, 0),
    };

    painter->save();

    QLinearGradient linear;
    linear.setStart(-200, -200);
    linear.setFinalStop(200, 200);
    linear.setColorAt(0, QColor(0, 255, 255, 0));
    linear.setColorAt(1, QColor(0, 255, 255, 255));
    painter->setPen(Qt::NoPen);
    painter->setBrush(linear);
    painter->drawPie(-200, -200, 400, 400, 225 * 16, -(angle * this->speed) * 16);

    painter->restore();

    painter->save();

    painter->setBrush(QBrush(QColor(0, 0, 0, 255), Qt::SolidPattern));
    painter->rotate(-135 + this->speed * angle);
    painter->drawPolygon(point, 4);

    painter->restore();
}

void Widget::drawSpeed(QPainter *painter)
{
    painter->save();

    painter->setPen(QColor("#0"));
    //  繪製速度
    QFont font("Times", 10, QFont::Bold);
    font.setBold(true);
    font.setPixelSize(66);
    painter->setFont(font);
    painter->drawText(-60, 100, 120, 92, Qt::AlignCenter, QString::number(speed));
    painter->restore();
}

void Widget::timerEvent(QTimerEvent *e)
{
    int timerId = e->timerId();

    if(this->time_id == timerId) {
        if(this->status == 0) {
            this->speed += 1;
            if(this->speed >= 180)
                this->status = 1;
        }else {
            this->speed -= 1;
            if(this->speed <= 0)
                this->status = 0;

        }

        this->update();
    }
}

widget.cpp

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>


class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void paintEvent(QPaintEvent *event);
    void timerEvent(QTimerEvent *e);

private:
    void drawFrame(QPainter *painter, qreal angle);
    void drawPointer(QPainter *painter, qreal angle);
    void drawSpeed(QPainter *painter);


    int speed;
    int time_id;
    int status;

};
#endif // WIDGET_H

widegt.h

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

main.cpp

Tags: