說明
- 本文演示Qt版本: Qt5.14.
- 本文將使用QPainter一步一步繪制儀表盤:刻度、指針、刻度值
- 注意: 繪制順序,如果先繪制,則后來繪制的將會覆蓋住先前繪制的。
- 如果需要繪制半透明, 請設置QColor的第四個參數 alpha , 范圍: 0~255, 0 - 全透明, 255-不透明
樣式
- 比較粗糙, 后面在優化
- 這是工作上遇到的需要繪制的圖形。
- 藍色的是指針indicator
下面一步一步開始繪制
開始前之前
- 重寫函數paintEvent( QPaintEvent *event )。QtGuiApplication1繼承自QWidget.
- 設置反走樣(抗鋸齒)
QPainter painter( this );
painter.setRenderHint( QPainter::Antialiasing, true);
繪制圓
代碼
void QtGuiApplication1::draw_ellipse_( QPainter& painter )
{
painter.save();
/// -----------------------------------------------------------------
/// 設置畫筆顏色
painter.setPen(QPen(QColor(100, 200, 100), 2));
/// 直接繪制圓
painter.drawEllipse( circle_config_.start_x_, circle_config_.start_y_,
circle_config_.radius_ * 2, circle_config_.radius_ * 2 );
/// -----------------------------------------------------------------
painter.restore();
}
效果
繪制刻度
每10度繪制一個刻度線,每30度繪制一個較長的刻度線
代碼
void QtGuiApplication1::draw_dial_( QPainter& painter )
{
painter.save();
painter.setPen(QPen(QColor(200, 100, 200), 2));
/// 旋轉坐標
/// 第一個參數: 坐標起點X
/// 第二個參數: 坐標起點Y
/// 第三個參數: 相對當前坐標系 向目標坐標系需要旋轉多少度
/// 第四個參數: 是否繪制較長的刻度
auto draw_dial = [&]( const double& xxx, const double& yyy, const double& angle, const bool& is_longer )
{
int dial_len = 8;
if (true == is_longer)
dial_len = 30;
/// 重置坐標系
painter.resetTransform();
/// 將其移動到目標點
painter.translate(xxx, yyy );
/// 再旋轉指定角度
painter.rotate( angle );
/// 繪制刻度
painter.drawLine( QLine(QPoint(0, 0), QPoint(-dial_len, 0)) );
};
/// 圓。 360度, 每10度繪制一個刻度線
for (int angle = 0; angle <= 360; angle += 10)
{
double xxx = 0;
double yyy = 0;
/// 根據圓心得到園周邊的點坐標
get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, xxx, yyy, angle);
/// 每30°繪制一個長一點的刻度
bool is_longer = ( 0 == ( angle % 30 ) ? true : false );
/// 繪制刻度
draw_dial( xxx, yyy, angle, is_longer);
}
painter.restore();
}
效果
繪制指針
- 這里是用扇形標識指針
- 注意: X軸正方向是3點鍾反向
代碼
/// --------------------------------------------------------------------------------------------------------
/// @brief: 繪制指針
/// --------------------------------------------------------------------------------------------------------
void QtGuiApplication1::draw_indicator_( QPainter& painter )
{
painter.save();
/// -----------------------------------------------------------------
painter.setPen(QPen(QColor(0, 0, 239)));
QBrush brush( QBrush( QColor( 0, 0, 239, 100) ) );
painter.setBrush(brush);
/// 在矩形內切橢圓上畫,3點鍾為0度,逆時針為正,角度要乘以16(參考文檔)
/// 起點,
int startAngle = -3 * 16;
/// 圓弧的角度大小
int spanAngle = 6 * 16;
painter.drawPie(circle_config_.start_x_, circle_config_.start_y_, circle_config_.radius_ * 2, circle_config_.radius_ * 2,
startAngle, spanAngle);
/// -----------------------------------------------------------------
painter.restore();
}
效果
繪制刻度數字
- 刻度數字其實也是繪制在圓周邊上的,比如,上圖的中的半徑為R,而刻度文字所在圓的半徑則是 0.8倍R,這樣就可以將文字繪制圈圈內了。
代碼
void QtGuiApplication1::draw_scale_number_( QPainter& painter )
{
painter.save();
/// -----------------------------------------------------------------
double rrr = circle_config_.radius_ * 0.8 ;
painter.setFont( QFont( "Microsoft Yahei", 10 ) );
QFontMetrics fm( painter.font() );
/// for (int index = -150; index <= 150; index += 30)
for (int index = 0; index < 360; index += 30)
{
double xxx = 0;
double yyy = 0;
/// 根據圓心得到園周邊的點坐標
get_dial_config_( circle_config_.center_x_, circle_config_.center_y_, rrr, xxx, yyy, index );
int dial_num = index - 270;
/// 3點為X軸, 故這里要特殊顯示為90 ~ 150
if (90 > index)
dial_num = index + 90;
/// 180不顯示
if (-180 == dial_num)
continue;
/// 下面這一步是為了得到繪制文字的寬和高, 從而決定文字的起點坐標
/// -----------------------------------------------------------------
/// 字體的高度
int fontw = fm.width(QString::number( dial_num));
/// 字體的寬度
int fonth = fm.height();
xxx -= fontw / 2.0;
yyy += fonth / 4.0;
/// -----------------------------------------------------------------
painter.drawText( xxx, yyy, QString::number( dial_num));
}
painter.restore();
}
效果
下面我們讓指針動起來
開始之前
- UI增加控件 QSlider,設置其范圍是0 ~360
- 關聯滑塊的信號,當值發生變化,則觸發重繪
void QtGuiApplication1::slot_slider_value_changed_( int value )
{
/// 指針當前角度
circle_config_.cur_angle_ = ( double ) value;
QWidget::update();
}
繪制指針
這里只需要將 起始角度 -3 改為 當前滑塊的值即可。 完整代碼如下
代碼
void QtGuiApplication1::draw_indicator_( QPainter& painter )
{
painter.save();
/// -----------------------------------------------------------------
painter.setPen(QPen(QColor(0, 0, 239)));
QBrush brush( QBrush( QColor( 0, 0, 239, 100) ) );
painter.setBrush(brush);
/// 在矩形內切橢圓上畫,3點鍾為0度,逆時針為正,角度要乘以16(參考文檔)
/// 起點,
int startAngle = (circle_config_.cur_angle_ - 3.0) * 16;
/// 圓弧的角度大小
int spanAngle = 6 * 16;
painter.drawPie(circle_config_.start_x_, circle_config_.start_y_, circle_config_.radius_ * 2, circle_config_.radius_ * 2,
startAngle, spanAngle);
/// -----------------------------------------------------------------
painter.restore();
}
效果
補充
- 實戰中,發現使用 扇形繪制的指針,指針填充漸變色的時候 不太理想, 因為按照公式計算的半徑得到的坐標總是不對。 於是 換了中繪制指針的方法: 畫兩根線, 兩根線中間用畫刷刷成指定的漸變色。
- 為了見刻度顯示在指針的上面, 因此,先與刻度繪制指針。
效果圖
代碼
這里是先繪制的指針,再繪制的周圍的刻度
draw_indicator2_(painter);
/// 繪制指針
//draw_indicator_(painter);
/// 繪制圓
draw_ellipse_(painter);
/// 繪制刻度
draw_dial_(painter);
/// 繪制刻度值
draw_scale_number_(painter);
繪制指針
/// --------------------------------------------------------------------------------------------------------
/// @brief: 繪制指針
/// --------------------------------------------------------------------------------------------------------
void QtGuiApplication1::draw_indicator2_(QPainter& painter)
{
painter.save();
/// -----------------------------------------------------------------
/// 計算指針兩條線的坐標, 方法: 以圓心為起點,旋轉指定角度繪制兩條只想, 夾角暫定為6度
/// 起始角度為當前角度 - 3(3 = 6 / 2.0)
auto draw_line = [&](const double& startx, const double& starty, const double& radius, const double& angle)
{
painter.resetTransform();
painter.translate(startx, starty);
painter.rotate(angle);
painter.drawLine(0, 0, radius, 0);
};
/// 因為是逆時針繪制的夾角,
draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ - 3);
draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ + 3);
/// -----------------------------------------------------------------
painter.restore();
}
加填充色填充
- 這里以填充漸變色, 還能學學 漸變色 QLinearGradient類的用法
- 老規矩, 上效果:
代碼
/// --------------------------------------------------------------------------------------------------------
/// @brief: 繪制指針
/// --------------------------------------------------------------------------------------------------------
void QtGuiApplication1::draw_indicator2_(QPainter& painter)
{
painter.save();
/// -----------------------------------------------------------------
/// 計算指針兩條線的坐標, 方法: 以圓心為起點,旋轉指定角度繪制兩條只想, 夾角暫定為6度
/// 起始角度為當前角度 - 3(3 = 6 / 2.0)
auto draw_line = [&](const double& startx, const double& starty, const double& radius, const double& angle)
{
painter.save();
painter.resetTransform();
painter.translate(startx, starty);
painter.rotate(angle);
painter.drawLine(0, 0, radius, 0);
painter.restore();
};
/// 因為是逆時針繪制的夾角,
draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ - 3);
draw_line(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, circle_config_.cur_angle_ + 3);
/// 計算漸變色的坐標
/// 漸變色的坐標起點是圓心 終點 就是 指針與圓周邊的交點
/// 這里的指針是用一個三角形代替指針的,當周圍的刻度繪制在指針的上面,哈哈遮住了 圓弧那段沒有填充色的部分
/// 指針的兩條直線之一A與圓周邊的焦點A
double lg_a_end_x = 0;
double lg_a_end_y = 0;
get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, lg_a_end_x, lg_a_end_y, circle_config_.cur_angle_ - 3);
/// 指針的兩條直線之一B與圓周邊的焦點A
double lg_b_end_x = 0;
double lg_b_end_y = 0;
get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, lg_b_end_x, lg_b_end_y, circle_config_.cur_angle_ + 3);
/// 三角區域
const QPointF triganle_points[3] =
{
QPointF(circle_config_.center_x_, circle_config_.center_y_),
QPointF(lg_a_end_x, lg_a_end_y),
QPointF(lg_b_end_x, lg_b_end_y)
};
/// 當前角度與圓的交點坐標,用於確定填充區域
double lg_end_x = 0;
double lg_end_y = 0;
get_dial_config_(circle_config_.center_x_, circle_config_.center_y_, circle_config_.radius_, lg_end_x, lg_end_y, circle_config_.cur_angle_);
QLinearGradient lg(circle_config_.center_x_, circle_config_.center_y_, lg_end_x, lg_end_y);
/// 0 ~ 0.3的區域填充 紅色
lg.setColorAt(0.3, QColor(230, 0, 0));
/// 0.3 ~ 0.6的區域填充綠色
lg.setColorAt(0.6, QColor(0, 230, 0));
/// 0.6~1之間的區域填充藍色
lg.setColorAt(1.0, QColor(0, 0, 230));
/// 設置划線的顏色
painter.setPen(QPen(QColor(30, 30, 30), 2));
/// 設置畫刷顏色
painter.setBrush(QBrush(lg));
painter.drawPolygon(triganle_points, 3, Qt::OddEvenFill);
/// -----------------------------------------------------------------
painter.restore();
}