作過安防視頻監控的同窗都清楚,在視頻監控系統軟件上均可以看到一個雲臺控制區域,能夠對球機進行下下左右等八個方位的運動控制,還能夠進行復位,通常都是美工做圖好,而後貼圖的形式加入到軟件中,好處是程序簡單,界面美工,主要取決於美工的美圖能力,缺點是對於各類分辨率的適應性稍微差點,須要不一樣的圖片切圖貼圖,除非默認作好的是大圖自適應看不出差異,可能大部分人所在的公司都是小公司,通常美工人員比較少甚至沒有,都須要程序員一人負責,甚至一開始就要考慮到各類分辨率的應用場景以及後期可能的換膚換色等。 以前作過不少自定義控件,大部分都採用了qpainter的形式繪製,有個好處就是自適應任意分辨率,因此思考着這個雲臺控制儀表盤也採用純painter繪製的形式,聽說純painter繪製還能夠輕鬆移植到qml中,這又堅決了我用qpainter繪製的決心。所謂心中有座標系,萬物皆painter。 觀察雲臺儀表盤下來,基本上就這幾部分組成,圓形底盤,八個角,中間部分按鈕,整個的控件的難點就在於八個角的定位,中間部分很好定位,並且八個角不是絕對的位置,都是相對於界面的寬高按照等比例自適應排列的。八個角的鼠標按下要作出對應的反應,發送出對應型號,網上大部分人都是切圖或者放置label或者按鈕來貼圖實現,綁定事件過濾器過濾鼠標按下而後再發出信號。我這裏爲了提高逼格,直接採用位置座標計算法。c++
#ifndef GAUGECLOUD_H #define GAUGECLOUD_H /** * 雲臺儀表盤控件 做者:feiyangqingyun(QQ:517216493) 2018-9-2 * 1:可設置背景顏色 * 2:可設置基準顏色 * 3:可設置邊框顏色 * 4:可設置文本顏色 * 5:可識別每一個角度+中間 鼠標按下併發出信號 * 6:可設置八個角的圖標和中間圖標,隨便換 * 7:內置4種雲臺風格 黑色+白色+藍色+紫色 * 8:支持拓展鼠標進入離開時的切換 * 9:精準識別內圓區域鼠標按下,而不是圓的矩形區域 * 10:支持長按連續觸發,支持設定延時間隔和執行間隔 */ #include <QWidget> #ifdef quc #if (QT_VERSION < QT_VERSION_CHECK(5,7,0)) #include <QtDesigner/QDesignerExportWidget> #else #include <QtUiPlugin/QDesignerExportWidget> #endif class QDESIGNER_WIDGET_EXPORT GaugeCloud : public QWidget #else class GaugeCloud : public QWidget #endif { Q_OBJECT Q_ENUMS(CloudStyle) Q_PROPERTY(QColor baseColor READ getBaseColor WRITE setBaseColor) Q_PROPERTY(QColor bgColor READ getBgColor WRITE setBgColor) Q_PROPERTY(QColor arcColor READ getArcColor WRITE setArcColor) Q_PROPERTY(QColor borderColor READ getBorderColor WRITE setBorderColor) Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor) Q_PROPERTY(QColor enterColor READ getEnterColor WRITE setEnterColor) Q_PROPERTY(QColor pressColor READ getPressColor WRITE setPressColor) Q_PROPERTY(QString iconText READ getIconText WRITE setIconText) Q_PROPERTY(QString centerText READ getCenterText WRITE setCenterText) Q_PROPERTY(CloudStyle cloudStyle READ getCloudStyle WRITE setCloudStyle) Q_PROPERTY(bool autoRepeat READ getAutoRepeat WRITE setAutoRepeat) Q_PROPERTY(int autoRepeatDelay READ getAutoRepeatDelay WRITE setAutoRepeatDelay) Q_PROPERTY(int autoRepeatInterval READ getAutoRepeatInterval WRITE setAutoRepeatInterval) public: enum CloudStyle { CloudStyle_Black = 0, //黑色風格 CloudStyle_White = 1, //白色風格 CloudStyle_Blue = 2, //藍色風格 CloudStyle_Purple = 3 //紫色風格 }; explicit GaugeCloud(QWidget *parent = 0); ~GaugeCloud(); protected: void enterEvent(QEvent *); void leaveEvent(QEvent *); void mousePressEvent(QMouseEvent *); void mouseReleaseEvent(QMouseEvent *); void mouseMoveEvent(QMouseEvent *); void paintEvent(QPaintEvent *); void drawCircle(QPainter *painter, int radius, const QBrush &brush); void drawArc(QPainter *painter); void drawText(QPainter *painter); private: QColor bgColor; //背景顏色 QColor baseColor; //基準顏色 QColor arcColor; //圓弧顏色 QColor borderColor; //邊框顏色 QColor textColor; //文字顏色 QColor enterColor; //懸停文字顏色 QColor pressColor; //按下文字顏色 QString iconText; //八個角圖標 QString centerText; //中間圖標 CloudStyle cloudStyle; //雲臺樣式 bool autoRepeat; //是否重複執行按下 int autoRepeatDelay; //延時執行按下動做 int autoRepeatInterval; //重複執行間隔 bool enter; //鼠標是否進入 bool pressed; //鼠標是否按下 bool inCenter; //是否在內圓中 QPoint lastPoint; //鼠標按下處的座標 QRectF centerRect; //中間區域 QRectF leftRect; //左側圖標區域 QRectF topRect; //上側圖標區域 QRectF rightRect; //右側圖標區域 QRectF bottomRect; //下側圖標區域 QRectF leftTopRect; //左上角圖標區域 QRectF rightTopRect; //右上角圖標區域 QRectF leftBottomRect; //左下角圖標區域 QRectF rightBottomRect; //右下角圖標區域 QFont iconFont; //圖形字體 int position; //最後按下的位置 QTimer *timer; //定時器觸發長按 private slots: void checkPressed(); private: double twoPtDistance(const QPointF &pt1, const QPointF &pt2); public: QColor getBgColor() const; QColor getBaseColor() const; QColor getArcColor() const; QColor getBorderColor() const; QColor getTextColor() const; QColor getEnterColor() const; QColor getPressColor() const; QString getIconText() const; QString getCenterText() const; CloudStyle getCloudStyle() const; bool getAutoRepeat() const; int getAutoRepeatDelay() const; int getAutoRepeatInterval() const; QSize sizeHint() const; QSize minimumSizeHint() const; public Q_SLOTS: //設置背景顏色 void setBgColor(const QColor &bgColor); //設置基準顏色 void setBaseColor(const QColor &baseColor); //設置圓弧顏色 void setArcColor(const QColor &arcColor); //設置邊框顏色 void setBorderColor(const QColor &borderColor); //設置文本顏色 void setTextColor(const QColor &textColor); //設置懸停文本顏色 void setEnterColor(const QColor &enterColor); //設置按下文本顏色 void setPressColor(const QColor &pressColor); //設置八個角圖標 void setIconText(const QString &iconText); //設置中間圖標 void setCenterText(const QString ¢erText); //設置雲臺樣式 void setCloudStyle(const CloudStyle &cloudStyle); //設置是否啓用長按重複執行 void setAutoRepeat(bool autoRepeat); //設置延時執行長按時間 void setAutoRepeatDelay(int autoRepeatDelay); //設置長按重複執行間隔 void setAutoRepeatInterval(int autoRepeatInterval); Q_SIGNALS: //鼠標按下的區域,共9個,從0-8依次表示底部/左下角/左側/左上角/頂部/右上角/右側/右下角/中間 void mousePressed(int position); //鼠標鬆開 void mouseReleased(int position); }; #endif //GAUGECLOUD_H
void GaugeCloud::paintEvent(QPaintEvent *) { int width = this->width(); int height = this->height(); int side = qMin(width, height); //以中心點爲基準,分別計算八方位區域和中間區域 QPointF center = this->rect().center(); double centerSize = (double)side / ((double)100 / 30); double iconSize = (double)side / ((double)100 / 10); double offset1 = 3.6; double offset2 = 2.65; //計算當前按下點到中心點的距離,若是小於內圓的半徑則認爲在內圓中 double offset = twoPtDistance(lastPoint, this->rect().center()); inCenter = (offset <= centerSize / 2); //中間區域 centerRect = QRectF(center.x() - centerSize / 2, center.y() - centerSize / 2, centerSize, centerSize); //左側圖標區域 leftRect = QRectF(center.x() - iconSize * offset1, center.y() - iconSize / 2, iconSize, iconSize); //上側圖標區域 topRect = QRectF(center.x() - iconSize / 2, center.y() - iconSize * offset1, iconSize, iconSize); //右側圖標區域 rightRect = QRectF(center.x() + iconSize * (offset1 - 1), center.y() - iconSize / 2, iconSize, iconSize); //下側圖標區域 bottomRect = QRectF(center.x() - iconSize / 2, center.y() + iconSize * (offset1 - 1), iconSize, iconSize); //左上角圖標區域 leftTopRect = QRectF(center.x() - iconSize * offset2, center.y() - iconSize * offset2, iconSize, iconSize); //右上角圖標區域 rightTopRect = QRectF(center.x() + iconSize * (offset2 - 1), center.y() - iconSize * offset2, iconSize, iconSize); //左下角圖標區域 leftBottomRect = QRectF(center.x() - iconSize * offset2, center.y() + iconSize * (offset2 - 1), iconSize, iconSize); //右下角圖標區域 rightBottomRect = QRectF(center.x() + iconSize * (offset2 - 1), center.y() + iconSize * (offset2 - 1), iconSize, iconSize); //繪製準備工做,啓用反鋸齒,平移座標軸中心,等比例縮放 QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); painter.translate(width / 2, height / 2); painter.scale(side / 200.0, side / 200.0); if (cloudStyle == CloudStyle_Black) { //繪製外圓背景 drawCircle(&painter, 99, bgColor); //繪製圓弧 drawArc(&painter); //繪製中間圓盤背景 drawCircle(&painter, 83, baseColor); //繪製內圓背景 drawCircle(&painter, 40, arcColor); //繪製內圓邊框 drawCircle(&painter, 33, borderColor); //繪製內圓 drawCircle(&painter, 30, inCenter ? bgColor : baseColor); } else if (cloudStyle == CloudStyle_White) { //繪製外圓背景 drawCircle(&painter, 99, QColor(249, 249, 249)); //設置圓錐漸變 QConicalGradient gradient(0, 0, 100); gradient.setColorAt(0, QColor(34, 163, 169)); gradient.setColorAt(0.4, QColor(240, 201, 136)); gradient.setColorAt(0.7, QColor(211, 77, 37)); gradient.setColorAt(1, QColor(34, 163, 169)); //繪製彩色外圓 drawCircle(&painter, 90, gradient); //繪製中間圓盤背景 drawCircle(&painter, 83, QColor(245, 245, 245)); //繪製內圓背景 drawCircle(&painter, 33, QColor(208, 208, 208)); //繪製內圓邊框 drawCircle(&painter, 32, QColor(208, 208, 208)); //繪製內圓 drawCircle(&painter, 30, inCenter ? QColor(255, 255, 255) : QColor(245, 245, 245)); } else if (cloudStyle == CloudStyle_Blue) { //設置圓錐漸變 QConicalGradient gradient(0, 0, 100); gradient.setColorAt(0, QColor(79, 163, 219)); gradient.setColorAt(0.4, QColor(227, 183, 106)); gradient.setColorAt(0.7, QColor(217, 178, 109)); gradient.setColorAt(1, QColor(79, 163, 219)); //繪製色彩外圓 drawCircle(&painter, 99, gradient); //繪製中間圓盤背景 drawCircle(&painter, 91, QColor(31, 66, 98)); //繪製內圓背景 drawCircle(&painter, 33, QColor(23, 54, 81)); //繪製內圓邊框 drawCircle(&painter, 30, QColor(150, 150, 150)); //繪製內圓 drawCircle(&painter, 30, inCenter ? QColor(35, 82, 133) : QColor(34, 73, 115)); } else if (cloudStyle == CloudStyle_Purple) { //設置圓錐漸變 QConicalGradient gradient(0, 0, 100); gradient.setColorAt(0, QColor(87, 87, 155)); gradient.setColorAt(0.4, QColor(129, 82, 130)); gradient.setColorAt(0.7, QColor(54, 89, 166)); gradient.setColorAt(1, QColor(87, 87, 155)); //繪製色彩外圓 drawCircle(&painter, 99, gradient); //繪製中間圓盤背景 drawCircle(&painter, 91, QColor(55, 55, 92)); //繪製內圓背景 drawCircle(&painter, 33, QColor(49, 48, 82)); //繪製內圓邊框 drawCircle(&painter, 30, QColor(82, 78, 131)); //繪製內圓 drawCircle(&painter, 30, inCenter ? QColor(85, 81, 137) : QColor(62, 59, 103)); } //繪製八方位+中間圖標 drawText(&painter); #if 0 //重置座標系,並繪製八方位區域及中間區域,判斷是否正確 painter.resetMatrix(); painter.resetTransform(); painter.setPen(Qt::white); painter.drawRect(centerRect); painter.drawRect(leftRect); painter.drawRect(topRect); painter.drawRect(rightRect); painter.drawRect(bottomRect); painter.drawRect(leftTopRect); painter.drawRect(rightTopRect); painter.drawRect(leftBottomRect); painter.drawRect(rightBottomRect); #endif } void GaugeCloud::drawCircle(QPainter *painter, int radius, const QBrush &brush) { painter->save(); painter->setPen(Qt::NoPen); painter->setBrush(brush); //繪製圓 painter->drawEllipse(-radius, -radius, radius * 2, radius * 2); painter->restore(); } void GaugeCloud::drawArc(QPainter *painter) { int radius = 91; painter->save(); painter->setBrush(Qt::NoBrush); QPen pen; pen.setWidthF(10); pen.setColor(arcColor); painter->setPen(pen); QRectF rect = QRectF(-radius, -radius, radius * 2, radius * 2); painter->drawArc(rect, 0 * 16, 360 * 16); painter->restore(); } void GaugeCloud::drawText(QPainter *painter) { bool ok; int radius = 100; painter->save(); //判斷當前按下座標是否在中心區域,按下則文本不一樣顏色 if (inCenter) { if (pressed) { position = 8; if (autoRepeat) { QTimer::singleShot(autoRepeatDelay, timer, SLOT(start())); } emit mousePressed(position); } painter->setPen(pressed ? pressColor : enterColor); } else { painter->setPen(textColor); } QFont font; font.setPixelSize(30); #if (QT_VERSION >= QT_VERSION_CHECK(4,8,0)) font.setHintingPreference(QFont::PreferNoHinting); #endif painter->setFont(font); //繪製中間圖標 QRectF centerRect(-radius, -radius, radius * 2, radius * 2); QString centerText = this->centerText.replace("0x", ""); QChar centerChar = QChar(centerText.toInt(&ok, 16)); painter->drawText(centerRect, Qt::AlignCenter, centerChar); //繪製八方位圖標 radius = 70; int offset = 15; int steps = 8; double angleStep = 360.0 / steps; font.setPixelSize(28); painter->setFont(font); //從下側圖標開始繪製,順時針旋轉 QRect iconRect(-offset / 2, radius - offset, offset, offset); QString iconText = this->iconText.replace("0x", ""); QChar iconChar = QChar(iconText.toInt(&ok, 16)); for (int i = 0; i < steps; i++) { //判斷鼠標按下的是哪一個區域 bool contains = false; if (bottomRect.contains(lastPoint) && i == 0) { contains = true; } else if (leftBottomRect.contains(lastPoint) && i == 1) { contains = true; } else if (leftRect.contains(lastPoint) && i == 2) { contains = true; } else if (leftTopRect.contains(lastPoint) && i == 3) { contains = true; } else if (topRect.contains(lastPoint) && i == 4) { contains = true; } else if (rightTopRect.contains(lastPoint) && i == 5) { contains = true; } else if (rightRect.contains(lastPoint) && i == 6) { contains = true; } else if (rightBottomRect.contains(lastPoint) && i == 7) { contains = true; } if (contains) { if (pressed) { position = i; if (autoRepeat) { QTimer::singleShot(autoRepeatDelay, timer, SLOT(start())); } emit mousePressed(position); } painter->setPen(pressed ? pressColor : enterColor); } else { painter->setPen(textColor); } painter->drawText(iconRect, Qt::AlignCenter, iconChar); painter->rotate(angleStep); } painter->restore(); }