Qt编写自定义控件:QWidget版Tumbler

#ifndef TUMBLERWIDGET_H
#define TUMBLERWIDGET_H

#include <QWidget>

class TumblerWidget : public QWidget
{
    Q_OBJECT

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

protected:
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent*event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;

private:
    struct TumblerWidgetPrivate * d_ptr;
};

#endif // TUMBLERWIDGET_H
#include "tumblerwidget.h"
#include <QPainter>
#include <QPaintEvent>
#include <QDebug>

struct TumblerWidgetPrivate
{
    QStringList list;
    QVector<QRect> rectVector;
    QRect centerRect;
    int currentIndex{0};
    TumblerWidget * q_ptr;
    int itemHeight{60};
    bool needResetSize{false};
    int isPressed : 1;
    QPoint pressPos;

    void findCurrentIndex();
    void alignRectangle();
    void changeInMoveing(int change_Y);
    void resetRects();
};

TumblerWidget::TumblerWidget(QWidget *parent)
    : QWidget(parent)
{
    auto font = this->font();
    font.setPixelSize(24);
    setFont(font);

    setMouseTracking(true);

    d_ptr = new TumblerWidgetPrivate;
    d_ptr->q_ptr = this;
    for(int i = 0;i < 31;++i)
    {
        d_ptr->list << QString("item%1").arg(i);
    }

    d_ptr->rectVector.resize(d_ptr->list.size());
}

TumblerWidget::~TumblerWidget()
{
    delete d_ptr;
}

void TumblerWidgetPrivate::findCurrentIndex()
{
    int maxIntersectArea = -1;
    int maxIntersectIndex = -1;
    int closestDistance = std::numeric_limits<int>::max();
    int closestIndex = -1;

    for (int i = 0; i < rectVector.size(); ++i)
    {
        const QRect& currentRect = rectVector.at(i);
        if (currentRect.intersects(centerRect))
        {
            int area = currentRect.intersected(centerRect).width() * currentRect.intersected(centerRect).height();
            if (area > maxIntersectArea)
            {
                maxIntersectArea = area;
                maxIntersectIndex = i;
            }
        }
        // 计算与中心矩形最近的矩形
        int distance = qAbs(centerRect.center().y() - currentRect.center().y());
        if (distance < closestDistance)
        {
            closestDistance = distance;
            closestIndex = i;
        }
    }

    if (maxIntersectIndex != -1)
    {
        currentIndex = maxIntersectIndex;
    }
    else
    {
        currentIndex = closestIndex;
    }
}

//对齐
void TumblerWidgetPrivate::alignRectangle()
{
    auto rectWidth = q_ptr->rect().width();

    for(int i = 0;i < rectVector.size();++i)
    {
        if(i < currentIndex)
        {
            rectVector[i] = QRect(0,centerRect.y() - (currentIndex - i) * itemHeight,rectWidth,itemHeight);
        }
        else if(i == currentIndex)
        {
            rectVector[i] = centerRect;
        }
        else
        {
            rectVector[i] = QRect(0,centerRect.y() + (i - currentIndex) * itemHeight,rectWidth,itemHeight);
        }
    }
}

void TumblerWidgetPrivate::changeInMoveing(int change_Y)
{
    auto rectWidth = q_ptr->rect().width();
    for(int i = 0;i < rectVector.size();++i)
    {
        if(i < currentIndex)
        {
            rectVector[i] = QRect(0,centerRect.y() - (currentIndex - i) * itemHeight - change_Y,rectWidth,itemHeight);
        }
        else if(i == currentIndex)
        {
            rectVector[i] = QRect(0,centerRect.y() - change_Y,rectWidth,itemHeight);
        }
        else
        {
            rectVector[i] = QRect(0,centerRect.y() + (i - currentIndex) * itemHeight - change_Y,rectWidth,itemHeight);
        }
    }
}

void TumblerWidgetPrivate::resetRects()
{
    auto rect = q_ptr->rect();
    auto centerRect_Y = (rect.height() - itemHeight) / 2;
    centerRect = QRect(0,centerRect_Y,rect.width(),itemHeight);
    changeInMoveing(0);
    needResetSize = false;
}

void TumblerWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::black);

    if(Q_UNLIKELY(d_ptr->needResetSize))
    {
        d_ptr->resetRects();
    }
    for(int i = 0;i < d_ptr->rectVector.size();++i)
    {
        painter.drawText(d_ptr->rectVector[i],Qt::AlignCenter,d_ptr->list[i]);
    }
    painter.fillRect(d_ptr->centerRect,QColor("#6611aa11"));
}

void TumblerWidget::resizeEvent(QResizeEvent *event)
{
    d_ptr->needResetSize = true;;
    auto newWidth = event->size().width();
    for(int i = 0;i < d_ptr->rectVector.size();++i)
    {
        auto & rect = d_ptr->rectVector[i];
        rect.setWidth(newWidth);
    }
    QWidget::resizeEvent(event);
}

void TumblerWidget::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        d_ptr->pressPos = event->pos();
        d_ptr->isPressed = true;
        update();
    }
}

void TumblerWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if(d_ptr->isPressed)
    {
        d_ptr->isPressed = false;
        d_ptr->findCurrentIndex();
        d_ptr->alignRectangle();
        update();
    }
}

void TumblerWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(d_ptr->isPressed)
    {
        auto cha = d_ptr->pressPos - event->pos();
        d_ptr->changeInMoveing(cha.y());
        update();
    }
}

相关: QML控件类型:Tumbler