自定义的Qt轮播图控件

该控件是模仿了一个名叫QCoolPage的开源项目里的轮播图控件,但是实现方式跟它的完全不同。QCoolPage里是用QPushButton和QLabel加上自定义styleSheet实现的;而我是用自定义控件加QPainter自己绘制的。如果想看它的具体实现方法可以网上搜索QCoolPage。我这里的实现是一次只显示5张图(总的图片数量可以超过5张),点击鼠标可以轮播图片。实现类对外提供了切换图片的函数,也可以用定时器定时切换图片。在VS2015和Qt5.9上测试通过。通过此案例可以学习较复杂的Qt动画。下面是效果图:

上代码,头文件:

class MPicturePlayer : public QWidget
{
    Q_OBJECT

public:
    MPicturePlayer(QWidget* parent = 0);
    void append(const QImage& iimage);
    void play(bool isToRight);

private:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void setMoveProgress(qreal pro);
    void setOpacity(qreal opac);
    QRectF paintStand(QPainter* painter, const QSize& baseSize, int i);
    QRectF paintMoveRight(QPainter* painter, const QSize& baseSize, int i);
    QRectF paintMoveLeft(QPainter* painter, const QSize& baseSize, int i);

private:
    static const QPointF vecl; /* 左侧3张图中心移动向量 */
    static const QPointF vecr; /* 右侧3张图中心移动向量 */
    static const qreal lengths[]; /* 每张图片距中心的距离 */
    static const qreal scales[]; /* 每张图片的缩放系数 */
    int currIndex;
    qreal moveRate;
    qreal opacity;
    QPoint pressPt;
    QRectF rect2Or4; /* 图片2或4的UI矩形 */
    QVector<QImage> images;
};

CPP文件:

const QPointF MPicturePlayer::vecl(0.99619469, -0.08715574); /* 左侧3张图中心移动向量 */
const QPointF MPicturePlayer::vecr(0.99619469, 0.08715574); /* 右侧3张图中心移动向量 */
const qreal MPicturePlayer::lengths[] = { -340, -220, -100, 0, 100, 220, 340 }; /* 每张图片距中心的距离 */
const qreal MPicturePlayer::scales[] = { 0.3, 0.6, 0.85, 1, 0.85, 0.6, 0.3 }; /* 每张图片的缩放系数 */

MPicturePlayer::MPicturePlayer(QWidget* parent) :
    QWidget(parent)
{
    currIndex = 0;
    moveRate = 0;
    opacity = 0;
}

void MPicturePlayer::append(const QImage& iimage)
{
    if (images.empty())
    {
        images.push_back(iimage);
        return;
    }
    QSize exist = images.first().size();
    if (exist == iimage.size())
    {
        images.push_back(iimage);
    }
    else
    {
        images.push_back(iimage.scaled(exist));
    }
}

//---------------------------------------------------------------------------------------
// 这里的图片轮播一次只显示5张图,点击鼠标可以轮播图片。为了绘制动画,我们定义了7个
// 数据(lengths和scales),正常情况最边上2个不显示图片。画控件的时候,从两边向中间
// 绘制以保证中间那张图在最上方。见下面的变量indexs。
// 另外,图片大小要配合控件大小才能有良好的显示效果。
//---------------------------------------------------------------------------------------
void MPicturePlayer::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(0, 0, width(), height(), Qt::black);
    int count = images.size();
    if (count == 0)
    {
        return; /* 无图 */
    }
    if (moveRate == 1.0)
    {
        currIndex = (currIndex + count - 1) % count;
        moveRate = 0;
    }
    if (moveRate == -1.0)
    {
        currIndex = (currIndex + 1) % count;
        moveRate = 0;
    }
    QSize baseSize = images.first().size().scaled(size() * 0.6, Qt::KeepAspectRatio);
    int indexs[] = { 0, 6, 1, 5, 2, 4, 3 }; /* 绘制图片的顺序 */
    if (moveRate > 0)
    {
        /* 默认的indexs是先画左2后画右4 */
        /* 这会导致右4在上方,从而使动画出现明显的可见的顿挫感 */
        /* 所以在图片右移时先画右4后画左2 */
        std::swap(indexs[4], indexs[5]);
    }
    for (auto i : indexs)
    {
        QRectF thisRect;
        if (moveRate == 0 && i != 0 && i != 6)
        {
            thisRect = paintStand(&painter, baseSize, i);
        }
        if (moveRate > 0 && i != 6) /* 图片向右移 */
        {
            thisRect = paintMoveRight(&painter, baseSize, i);
        }
        if (moveRate < 0 && i != 0) /* 图片向左移 */
        {
            thisRect = paintMoveLeft(&painter, baseSize, i);
        }
        if (i != 3)
        {
            int alpha = 32 * abs(i - 3);
            painter.fillRect(thisRect, QColor(0, 0, 0, alpha));
        }
    }
}

QRectF MPicturePlayer::paintStand(QPainter* painter, const QSize& baseSize, int i)
{
    int count = images.size();
    QPointF center(width() * 0.5, height() * 0.5);
    QPointF thisPos = center + lengths[i] * (i < 3 ? vecl : vecr);
    QSize thisSize = baseSize * scales[i];
    QRectF thisRect;
    thisRect.setX(thisPos.x() - thisSize.width() * 0.5);
    thisRect.setY(thisPos.y() - thisSize.height() * 0.5);
    thisRect.setSize(thisSize);
    int hitIndex = (currIndex + count + (i - 3)) % count;
    painter->drawImage(thisRect, images[hitIndex]);
    return thisRect;
}

//---------------------------------------------------------------------------------------
// 图片向右移动
//---------------------------------------------------------------------------------------
QRectF MPicturePlayer::paintMoveRight(QPainter* painter, const QSize& baseSize, int i)
{
    int count = images.size();
    QPointF center(width() * 0.5, height() * 0.5);
    qreal value = lengths[i] + (lengths[i + 1] - lengths[i]) * moveRate;
    qreal scale = scales[i] + (scales[i + 1] - scales[i]) * moveRate;
    QPointF thisPos = center + value * (i < 3 ? vecl : vecr);
    QSize thisSize = baseSize * scale;
    QRectF thisRect;
    thisRect.setX(thisPos.x() - thisSize.width() * 0.5);
    thisRect.setY(thisPos.y() - thisSize.height() * 0.5);
    thisRect.setSize(thisSize);
    if (i == 2)
    {
        rect2Or4 = thisRect;
    }
    int hitIndex = (currIndex + count + (i - 3)) % count;
    QImage thisImage = images[hitIndex];
    if (i == 3) /* 3必须在2后面绘制 */
    {
        QRectF cross = thisRect.intersected(rect2Or4);
        qreal uiCross = cross.width(); /* 这里是左侧重叠 */
        QRectF uiLeft(thisRect.x(), thisRect.y(), uiCross, thisRect.height());
        QRectF uiRight(thisRect.x() + uiCross, thisRect.y(), thisRect.width() - uiCross, thisRect.height());
        qreal imCross = uiCross * (thisImage.width() / thisRect.width());
        QRectF imLeft(0, 0, imCross, thisImage.height());
        QRectF imRight(imCross, 0, thisImage.width() - imCross, thisImage.height());
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(uiLeft, thisImage, imLeft);
        painter->restore();
        painter->drawImage(uiRight, thisImage, imRight);
    }
    else if (i == 5)
    {
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(thisRect, thisImage);
        painter->restore();
    }
    else
    {
        painter->drawImage(thisRect, thisImage);
    }
    return thisRect;
}

//---------------------------------------------------------------------------------------
// 图片向左移动
//---------------------------------------------------------------------------------------
QRectF MPicturePlayer::paintMoveLeft(QPainter* painter, const QSize& baseSize, int i)
{
    int count = images.size();
    QPointF center(width() * 0.5, height() * 0.5);
    qreal value = lengths[i] + (lengths[i - 1] - lengths[i]) * -moveRate;
    qreal scale = scales[i] + (scales[i - 1] - scales[i]) * -moveRate;
    QPointF thisPos = center + value * (i > 3 ? vecr : vecl);
    QSize thisSize = baseSize * scale;
    QRectF thisRect;
    thisRect.setX(thisPos.x() - thisSize.width() * 0.5);
    thisRect.setY(thisPos.y() - thisSize.height() * 0.5);
    thisRect.setSize(thisSize);
    if (i == 4)
    {
        rect2Or4 = thisRect;
    }
    int hitIndex = (currIndex + count + (i - 3)) % count;
    QImage thisImage = images[hitIndex];
    if (i == 3)
    {
        QRectF cross = thisRect.intersected(rect2Or4);
        qreal uiCross = cross.width(); /* 这里是右侧重叠 */
        QRectF uiLeft(thisRect.x(), thisRect.y(), thisRect.width() - uiCross, thisRect.height());
        QRectF uiRight(thisRect.x() + thisRect.width() - uiCross, thisRect.y(), uiCross, thisRect.height());
        qreal imCross = uiCross * (thisImage.width() / thisRect.width());
        QRectF imLeft(0, 0, thisImage.width() - imCross, thisImage.height());
        QRectF imRight(thisImage.width() - imCross, 0, imCross, thisImage.height());
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(uiRight, thisImage, imRight);
        painter->restore();
        painter->drawImage(uiLeft, thisImage, imLeft);
    }
    else if (i == 1)
    {
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(thisRect, thisImage);
        painter->restore();
    }
    else
    {
        painter->drawImage(thisRect, thisImage);
    }
    return thisRect;
}

void MPicturePlayer::setMoveProgress(qreal pro)
{
    moveRate = pro; /* [0,1] */
    update();
}

void MPicturePlayer::setOpacity(qreal opac)
{
    opacity = opac; /* [0,1] */
    update();
}

void MPicturePlayer::mousePressEvent(QMouseEvent *event)
{
    pressPt = event->pos();
}

void MPicturePlayer::mouseReleaseEvent(QMouseEvent *event)
{
    if (pressPt == event->pos())
    {
        bool isClickLeft = (pressPt.x() < width() / 2);
        play(isClickLeft); /* 点的太快会有BUG */
    }
}

void MPicturePlayer::play(bool isToRight)
{
    QParallelAnimationGroup* group = new QParallelAnimationGroup(this);
    QVariantAnimation* ani1 = new QVariantAnimation(group);
    ani1->setStartValue(0.0);
    ani1->setEndValue(isToRight ? 1.0 : -1.0);
    ani1->setDuration(200);
    connect(ani1, &QVariantAnimation::valueChanged, this,
        [this](const QVariant& value) { setMoveProgress(value.toDouble()); });
    QVariantAnimation* ani2 = new QVariantAnimation(group);
    ani2->setStartValue(1.0);
    ani2->setEndValue(0.0);
    ani2->setDuration(200);
    connect(ani2, &QVariantAnimation::valueChanged, this,
        [this](const QVariant& value) { setOpacity(value.toDouble()); });
    group->addAnimation(ani1);
    group->addAnimation(ani2);
    group->start(QVariantAnimation::DeleteWhenStopped);
}

 

posted @ 2022-12-07 20:17  兜尼完  阅读(384)  评论(0编辑  收藏  举报