带切换动画的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

源码

源码地址:点我下载

  • 18
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值