自定義的Qt輪播圖控制元件
阿新 • • 發佈:2022-12-07
該控制元件是模仿了一個名叫QCoolPage的開源專案裡的輪播圖控制元件,但是實現方式跟它的完全不同。QCoolPage裡是用QPushButton和QLabel加上自定義styleSheet實現的;而我是用自定義控制元件加QPainter自己繪製的。如果想看它的具體實現方法可以網上搜索QCoolPage。我這裡的實現是一次只顯示5張圖(總的圖片數量可以超過5張),點選滑鼠可以輪播圖片。實現類對外提供了切換圖片的函式,也可以用定時器定時切換圖片。在VS2015和Qt5.9上測試通過。通過此案例可以學習較複雜的Qt動畫。下面是效果圖:
上程式碼,標頭檔案:
class MPicturePlayer : publicQWidget { 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, inti); 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); }