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(); } }
#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
#include "widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }