signed

QiShunwang

“诚信为本、客户至上”

QT图形视图框架:自定义遥控器圆形按钮图形项

2020/12/30 13:03:55   来源:

效果:

代码:

#ifndef DISCBUTTONGRAPHICSPIXMAPITEM_H
#define DISCBUTTONGRAPHICSPIXMAPITEM_H

#include <QGraphicsObject>

class DiscButtonGraphicsPixmapItem : public QGraphicsObject
{
    Q_OBJECT
public:
    DiscButtonGraphicsPixmapItem(QString uuid,QGraphicsItem *parent = nullptr);
protected:
    QVariant itemChange(GraphicsItemChange change, const QVariant &value)override;
    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)override;
    virtual QRectF boundingRect()const override;
    void mousePressEvent(QGraphicsSceneMouseEvent *event)override;
    void hoverMoveEvent(QGraphicsSceneHoverEvent* event)override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent* event)override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent* event)override;
private:
    bool IsInResizeArea(const QPointF& pos);
    QSizeF itemSize;
    bool isResizing;  //是否正在改变大小的过程中
    QString uuid;
    QRect drawRect;//整个大圆的范围
    bool isPointInCir(const QPoint &point, const QRect & rect);
    QPainterPath fanShaped[4];
    QRect centerCircularRect;//中心圆按钮的范围
    QPainterPath gradientArc(int startAngle, int angleLength, int arcHeight);
    enum class pressBtnType //按下的是哪个按钮
    {
        up = 0,
        left,
        down,
        right,
        center,
        None
    };
    pressBtnType pressedBtn{pressBtnType::None};
};

#endif // DISCBUTTONGRAPHICSPIXMAPITEM_H
#include "discbuttongraphicspixmapitem.h"
#include <QGraphicsScene>
#include <QDebug>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <math.h>

const qreal g_cResizePos[] = {9, 6, 3};

DiscButtonGraphicsPixmapItem::DiscButtonGraphicsPixmapItem(QString uuid, QGraphicsItem *parent)
    :QGraphicsObject(parent)
{
    setAcceptHoverEvents(true);
    setFlag(QGraphicsItem::ItemIsMovable);
    setFlag(QGraphicsItem::ItemSendsScenePositionChanges);//图形项可发送位置变化信号
    setFlag(QGraphicsItem::ItemIsSelectable);
    setFlag(QGraphicsItem::ItemIsFocusable);
    this->uuid = uuid;
    this->itemSize = QSizeF(100,100);
    isResizing = false;
    setZValue(1);
}

QRectF DiscButtonGraphicsPixmapItem::boundingRect() const
{
    return  QRectF(0, 0, itemSize.width() + 10, itemSize.height() + 10);
}

bool DiscButtonGraphicsPixmapItem::isPointInCir(const QPoint & point,const QRect & rect)//判断点是否在圆范围内
{
    const QPoint & centerPoint = rect.center();
    int x = point.x() - centerPoint.x();
    int y = point.y() - centerPoint.y();
    if(sqrt(pow(x,2) + pow(y,2)) <= static_cast<double>(rect.width() / 2))
    {
        return true;
    }
    return false;
}

void DiscButtonGraphicsPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        if(IsInResizeArea(event->pos()))
            isResizing = true;

        QGraphicsObject::mousePressEvent(event);
        QPoint point = event->pos().toPoint();
        if(isPointInCir(point,drawRect))
        {
            if(isPointInCir(point,centerCircularRect))
            {
                pressedBtn = pressBtnType::center;
            }
            else
            {
                QPoint centerPoint = drawRect.center();
                double angle = atan2(point.y()-centerPoint.y(),point.x()-centerPoint.x()); //两点之间的角度(弧度)
                angle = -angle*(180 / 3.1415926); //0°~180° - -180°~0°
                if(angle < 0.0)
                {
                    angle = 360.0 - abs(angle);
                }
                if(angle < 45.0 || angle > 315.0)
                {
                    pressedBtn = pressBtnType::right;
                }
                else if(angle >= 45.0 && angle < 135.0)
                {
                    pressedBtn = pressBtnType::up;
                }
                else if(angle >= 135.0 && angle < 225.0)
                {
                    pressedBtn = pressBtnType::left;
                }
                else if(angle >= 225.0 && angle < 315.0)
                {
                    pressedBtn = pressBtnType::down;
                }
            }
            update();
        }
        return;
    }
    else if(event->button() == Qt::RightButton)
    {
        return;
    }
    return QGraphicsObject::mousePressEvent(event);
}

void DiscButtonGraphicsPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    if(pressedBtn != pressBtnType::None)
    {
        pressedBtn = pressBtnType::None;
        update();
    }

    if (event->button() == Qt::LeftButton && isResizing)
        isResizing = false;
    else
        QGraphicsObject::mouseReleaseEvent(event);
}

QVariant DiscButtonGraphicsPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if ((change == ItemPositionChange || change == ItemPositionHasChanged) && scene()) // 控件发生移动
    {
        QPointF newPos = value.toPointF();
        QRectF rect(0, 0, scene()->width(), scene()->height());
        if (!rect.contains(newPos))//左上角
        {
            newPos.setX(qMin(rect.width(), qMax(newPos.x(), 0.0)));
            newPos.setY(qMin(rect.height(), qMax(newPos.y(), 0.0)));

//            newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
//            newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
            return newPos;
        }

        QRectF thisRectF = boundingRect();
        QPointF nowPos = QPointF(newPos.x() + thisRectF.width(),newPos.y());
        if(!rect.contains(nowPos))//右上角
        {
            newPos.setX(rect.width() - thisRectF.width());
            this->setPos(newPos);
            return newPos;
        }

        nowPos = QPointF(newPos.x(),newPos.y() + thisRectF.height());
        if(!rect.contains(nowPos))//左下角
        {
            newPos.setY(rect.height() - thisRectF.height());
            this->setPos(newPos);
            return newPos;
        }
    }
    return QGraphicsItem::itemChange(change, value);
}

QPainterPath DiscButtonGraphicsPixmapItem::gradientArc(int startAngle, int angleLength, int arcHeight)
{
    QPainterPath path;
    path.moveTo(drawRect.center());
    path.arcTo(drawRect, startAngle, angleLength);

    QPainterPath subPath;
    subPath.addEllipse(drawRect.adjusted(arcHeight, arcHeight, -arcHeight, -arcHeight));

    // path为扇形 subPath为椭圆
    path -= subPath;
    return path;
}

void DiscButtonGraphicsPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget);
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing,true);
    painter->setRenderHint(QPainter::SmoothPixmapTransform,true);
    painter->setRenderHint(QPainter::TextAntialiasing,true);

    QRectF thisRectF = boundingRect();

    if(option->state & QStyle::State_Selected)
    {
        painter->setPen(QColor("#D8D8D8"));
        painter->drawRect(thisRectF);
        setZValue(2);
    }
    else
    {
        setZValue(1);
        painter->fillRect(thisRectF, QBrush(Qt::transparent));
    }

    QRect thisRect = thisRectF.toRect();
    QPoint centerPoint = thisRect.center();

    painter->save();
    painter->setPen(QColor("#222222"));
    painter->setBrush(QColor("#EAEAEA"));
    int radius = (std::min(thisRect.width(),thisRect.height()) - 10 * 2 ) / 2;
    drawRect = QRect(centerPoint.x()-radius, centerPoint.y()-radius, radius*2, radius*2);
    int arcHeight = radius / 2;

    fanShaped[0] = gradientArc(45,  90, arcHeight);//上
    fanShaped[1] = gradientArc(135, 90, arcHeight);//左
    fanShaped[2] = gradientArc(225, 90, arcHeight);//下
    fanShaped[3] = gradientArc(315, 90, arcHeight);//右

    for (int i = 0;i < 4;++i)
    {
        painter->drawPath(fanShaped[i]);
    }
    painter->restore();

    centerCircularRect = QRect(centerPoint.x() - (radius / 2), centerPoint.y() - (radius / 2), radius, radius).adjusted(2,2,-2,-2);
    painter->save();
    painter->setPen(Qt::transparent);
    painter->setBrush(QColor("#EAEAEA"));
    painter->drawEllipse(centerCircularRect);
    painter->restore();

    //绘制文字
    //左
    painter->save();
    QRect textRect = QRect(drawRect.x(),centerCircularRect.y(),radius / 2,radius);
    QPen p(Qt::SolidLine);
    p.setColor("#000000");
    p.setWidth(2);
    painter->setPen(p);
    QFont font = painter->font();
    font.setPixelSize(24);
    painter->setFont(font);
    painter->drawText(textRect, Qt::AlignCenter, "〈");//在此区域的中间位置绘制文字 〉

    //上
    textRect = QRect(centerCircularRect.x(),drawRect.y(),radius,radius / 2);
    painter->drawText(textRect, Qt::AlignCenter, "︿");

    //右
    textRect = QRect(centerCircularRect.topRight().x(),centerCircularRect.topRight().y(),radius / 2,radius);
    painter->drawText(textRect, Qt::AlignCenter, "〉");

    //下
    textRect = QRect(centerCircularRect.bottomLeft().x(),centerCircularRect.bottomLeft().y(),radius,radius / 2);
    painter->drawText(textRect, Qt::AlignCenter, "﹀");

    painter->drawText(centerCircularRect, Qt::AlignCenter, "OK");
    painter->restore();

    if(pressedBtn != pressBtnType::None)
    {
        painter->save();
        QColor slightlyOpaqueBlack(0, 0, 0, 63);
        painter->setBrush(slightlyOpaqueBlack);
        painter->setPen(Qt::transparent);
        if(pressedBtn == pressBtnType::center)
        {
            painter->drawEllipse(centerCircularRect);
        }
        else
        {
            int index = -1;
            index = static_cast<int>(pressedBtn);
            if(index >= 0)
            {
                painter->drawPath(fanShaped[index]);
            }
        }
        painter->restore();
    }

    if(option->state & QStyle::State_Selected)
    {
        qreal w = thisRectF.width();
        qreal h = thisRectF.height();
        painter->setPen(Qt::red);
        for (int i = 0; i < 3; ++i)//三角形
            painter->drawLine(static_cast<int>(w - g_cResizePos[i]) , static_cast<int>(h), static_cast<int>(w), static_cast<int>(h - g_cResizePos[i]));
    }

    painter->restore();
}

void DiscButtonGraphicsPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
{
    if (isResizing || (IsInResizeArea(event->pos()) && isSelected()))
        setCursor(Qt::SizeFDiagCursor);
    else
        setCursor(Qt::ArrowCursor);

    QGraphicsObject::hoverMoveEvent(event);
}

void DiscButtonGraphicsPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if (isResizing)
    {
        qreal w = event->pos().x();
        qreal h = event->pos().y();
        if (w > 100)
            itemSize.setWidth(w);
        if (h > 100)
            itemSize.setHeight(h);
        prepareGeometryChange();
    }
    else
    {
        QGraphicsObject::mouseMoveEvent(event);
    }
}

bool DiscButtonGraphicsPixmapItem::IsInResizeArea(const QPointF& pos)
{
    return (pos.x() - itemSize.width() + g_cResizePos[0]) > (itemSize.height() - pos.y());
}

设计图:

大圆的半径是中间小圆的2倍。

绘制文字的位置:

鼠标按下时根据按下位置与中心点的坐标确定这两点的水平夹角,再根据夹角的大小确定按的是哪个按钮:

绘制扇形:

QPainterPath DiscButtonGraphicsPixmapItem::gradientArc(int startAngle, int angleLength, int arcHeight)
{
    QPainterPath path;
    path.moveTo(drawRect.center());
    path.arcTo(drawRect, startAngle, angleLength);

    QPainterPath subPath;
    subPath.addEllipse(drawRect.adjusted(arcHeight, arcHeight, -arcHeight, -arcHeight));

    // path为扇形 subPath为椭圆
    path -= subPath;
    return path;
}

其中:

    QPainterPath path;
    path.moveTo(drawRect.center());
    path.arcTo(drawRect, startAngle, angleLength);

    QPainterPath subPath;
    subPath.addEllipse(drawRect.adjusted(arcHeight, arcHeight, -arcHeight, -arcHeight));

    path -= subPath;