前言
有些時候我們使用QStackedWidget想要切換動畫,就比如酷狗的這種效果:
使用自帶的QStackedWidget是無法達到動畫的切換效果的,所以爲了滿足這種需求我們可以繼承QStackedWidget並結合使用QPropertyAnimation就可以達到這種效果。
原理
我們知道,要切換QStackedWidget當前的顯示界面可以調用QStackedWidget::setCurrentIndex()函數來切換,這種切換是跳躍式的,所以我們的動畫就是在調用這個setCurrentIndex()函數之前開始,動畫結束之後再調用setCurrentIndex()函數,這樣就可以看到動畫了。
但是這中間的動畫過程要做什麼事呢?那當然就是將兩個Widget的過渡過程繪製出來了,在過渡完成之後再真正的切換QStackedWidget的顯示界面。
如上圖所示,線1的座標就是QPropertyAnimation所計算出來的,線1
將整個QStackedWidget分成左、右兩個部分,左邊繪製widget1
(QStackedWidget中當前顯示的Widget)的右半部分,右邊繪製widget2
(QStackedWidget中將要顯示的Widget)的左半部分,因爲線1
是變化的,這就產生了我們看到的切換動畫了
源碼
接着上面的原理,用代碼來說明,在這之前一些知識點:
- Widget::render(QPixmap) :這個函數的作用是將Widget的內容渲染成一張圖片,這就爲我們繪製兩個Widget提供了可能
- QPainter::drawPixmap(QRectF target, QPixmap &pixmap, QRectF source):這個函數的作用是將上面渲染得到的圖片繪製出來,參數說明:
- target:要繪製的目標區域
- pixmap:要繪製的圖片
- source:要繪製的圖片的區域
- 綜上:也就是說將pixmap的source部分繪製到目標(這裏是QStackedWidget)的target部分
上面的兩個函數說完了,感興趣的可以自己去學習下繪圖知識
下面看代碼,首先是頭文件:
class QAnimationStackedWidget : public QStackedWidget
{
Q_OBJECT
public:
explicit QAnimationStackedWidget(QWidget *parent = 0);
void paintEvent(QPaintEvent *);
//設置動畫持續的間隔
void setDuration(int );
~QAnimationStackedWidget();
signals:
public slots:
//屬性動畫值改變的槽
void valueChanged_slot(QVariant );
//動畫切換完成
void animationFinished();
//前一頁
void next();
//下一頁
void forward();
private:
void paintPrevious(QPainter &, int);
void paintNext(QPainter &, int);
private:
QPropertyAnimation *animation; //動畫框架
int duration; //動畫的持續時間
bool isAnimation; //是否正在動畫
QVariant currentValue; //被Animation動畫改變的值
int widgetCount; //保存當前StackWidget裏面的子成員數
int nextIndex; //下一個要切換的索引
};
上面是自定義的QAnimationStackedWidget類,裏面持有一個QPropertyAnimation對象,它的初始化部分:
QAnimationStackedWidget::QAnimationStackedWidget(QWidget *parent) :
QStackedWidget(parent)
{
isAnimation = false;
//設置默認的時間間隔
duration = 1000;
//初始化animation框架、並連接信號和槽
animation = new QPropertyAnimation(this, QByteArray());
connect(animation, SIGNAL(valueChanged(QVariant)), this, SLOT(valueChanged_slot(QVariant)));
connect(animation, SIGNAL(finished()), this, SLOT(animationFinished()));
}
然後聲明一個切換下一頁的槽函數:
void QAnimationStackedWidget::next()
{
//如果正在動畫,那麼return
if( isAnimation )
{
return;
}
isAnimation = true;
widgetCount = count();
int c = currentIndex();
//計算下一頁的索引
nextIndex = (c + 1) % widgetCount;
//隱藏當前的widget
widget(c)->hide();
//開始動畫並設置間隔和開始、結束值
QRect g = geometry();
int x = g.x();
int width = g.width();
animation->setStartValue(width);
animation->setEndValue(0);
animation->setDuration(duration);
animation->start();
}
上面的代碼是開啓動畫,因爲QPropertyAnimation::start()方法調用之後會發射valueChanged()信號,所以我們可以接收到改變了的值:
void QAnimationStackedWidget::valueChanged_slot(QVariant value)
{
currentValue = value;
update();
}
update又會觸發paintEvent函數,所以我們在paintEvent裏面可以將過渡的效果繪製出來:
void QAnimationStackedWidget::paintEvent(QPaintEvent *e)
{
if( isAnimation )
{
QPainter paint(this);
//繪製當前Widget
paintPrevious(paint, currentIndex());
//繪製下一個widget
paintNext(paint, nextIndex);
}
}
paintPrevious:
void QAnimationStackedWidget::paintPrevious(QPainter &paint, int currentIndex)
{
//獲得當前頁面的Widget
QWidget *w = widget(currentIndex);
QPixmap pixmap(w->size());
//將Widget的內容渲染到QPixmap對象中,即將Widget變成一張圖片
w->render(&pixmap);
QRect r = w->geometry();
//繪製當前的Widget
double value = currentValue.toDouble();
QRectF r1(0.0, 0.0, value, r.height());
QRectF r2(r.width() - value, 0, value, r.height());
paint.drawPixmap(r1, pixmap, r2);
}
paintNext():
void QAnimationStackedWidget::paintNext(QPainter &paint, int nextIndex)
{
QWidget *nextWidget = widget(nextIndex);
QRect r = geometry();
//這行代碼不加會有bug,第一次切換的時候,QStackedWidget並沒有爲child分配大小
nextWidget->resize(r.width(), r.height());
QPixmap nextPixmap(nextWidget->size());
nextWidget->render(&nextPixmap);
double value = currentValue.toDouble();
QRectF r1(value, 0.0, r.width() - value, r.height());
QRectF r2(0.0, 0.0, r.width() - value, r.height());
paint.drawPixmap(r1, nextPixmap, r2);
}
動畫完成,真正開始切換界面:
void QAnimationStackedWidget::animationFinished()
{
isAnimation = false;
widget(currentIndex())->show();
setCurrentIndex(nextIndex);
}
至此,效果已經出來了,下面看看效果:
資料
在實現這個效果的過程中有參考過這個博客:
源碼
源碼地址:點我下載