1. 程式人生 > 其它 >自定義的Qt輪播圖控制元件

自定義的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);
}