帶切換動畫的QStackedWidget

前言

有些時候我們使用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);

}

至此,效果已經出來了,下面看看效果:
這裏寫圖片描述

資料

在實現這個效果的過程中有參考過這個博客:

http://www.itdadao.com/articles/c15a360863p0.html

源碼

源碼地址:點我下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章