Qt 动画框架(01):The Animation Framework【官翻】

一、前言

画框架旨在提供一种创建动画和平滑GUI的简便方法。 通过对Qt属性进行动画处理,框架为动画小部件和其他QObject提供了极大的自由度。 该框架也可以与Graphics View框架一起使用。 Qt Quick也提供了动画框架中可用的许多概念,其中提供了定义动画的声明性方式。 获得的有关动画框架的许多知识都可以应用于Qt Quick。

本概述中,我们解释了其体系结构的基础。 我们还将展示该框架允许对QObject和图形项目进行动画处理的最常见技术的示例。

二、动画架构

本节中,我们将对动画框架的体系结构以及如何将其用于Qt属性进行动画化进行深入研究。 下图显示了动画框架中最重要的类。

画框架由基类QAbstractAnimation及其两个子类QVariantAnimation和QAnimationGroup组成。QAbstractAnimation是所有动画的祖先。它表示框架中所有动画通用的基本属性;值得注意的是,可以启动、停止和暂停动画的能力。它还接收时间更改通知。

画框架进一步提供了QPropertyAnimation类,它继承QVariantAnimation并执行Qt属性的动画,该属性是Qt元对象系统的一部分。该类使用简化曲线在属性上执行插值。因此,当您想要动画一个值时,您可以将它声明为一个属性并使您的类成为QObject。注意,这给了我们很大的自由来动画已经存在的小部件和其他qobject。

过构建QAbstractAnimations的树形结构,可以构建复杂的动画。树是使用QAnimationGroups构建的,它作为其他动画的容器。还要注意,组是QAbstractAnimation的子类,因此组本身可以包含其他组。

画框架可以单独使用,但也被设计为状态机框架的一部分(关于Qt状态机的介绍,请参阅状态机框架)。状态机提供了可以播放动画的特殊状态。当状态被输入或退出时,QState也可以设置属性,并且当给定QPropertyAnimation时,这个特殊的动画状态将在这些值之间插入。稍后我们将对此进行更详细的讨论。

幕后,动画由一个全局计时器控制,它向所有正在播放的动画发送更新。

关类在框架中的功能和角色的详细描述,请查阅它们的类描述。

三、动画框架中的类

以下的类提供了创建简单和复杂动画的框架,继承关系见下图:

Class简介
QAbstractAnimation所有动画的基础
QAnimationGroup动画组的抽象基类
QEasingCurve放松曲线来控制动画
QParallelAnimationGroup平行动画组
QPauseAnimation暂停QSequentialAnimationGroup
QPropertyAnimation的Qt属性
QSequentialAnimationGroup动画序列组
QTimeLine控制动画的时间轴
QVariantAnimation动画的基类

四、动画Qt属性

前一节所述,QPropertyAnimation类可以插入Qt属性。通常这个类应该用于值的动画;实际上,它的超类QVariantAnimation有一个空的updateCurrentValue()实现,并且不改变任何值,除非我们自己在valueChanged信号上改变它。

们选择动画Qt属性的一个主要原因是,它让我们可以自由地动画Qt API中已经存在的类。值得注意的是,QWidget类(我们也可以嵌入到QGraphicsView中)具有其边界、颜色等属性。让我们看一个小例子:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton button("Animated Button");
    button.show();

    QPropertyAnimation animation(&button, "pos");
    animation.setDuration(3000);
    animation.setStartValue(button.pos());
    animation.setEndValue(button.pos() + QPoint(100,100));

    QObject::connect(&button,&QPushButton::clicked,[&]{
        animation.start();
    });
    a.exec();
    return 0;
}

段代码将在3秒(3000毫秒)内将按钮从当前位置移动到以当前位置为基准偏移(100,100)的地方。
面的示例将在开始值和结束值之间进行线性插值。

可以在开始值和结束值之间设置值。插值会经过这些点:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton button("Animated Button");
    button.show();

    QPropertyAnimation animation(&button, "pos");
    animation.setDuration(3000);
    QPoint pos = button.pos();
    animation.setKeyValueAt(0, pos);
    animation.setKeyValueAt(0.8, pos + QPoint(100,100));
    animation.setKeyValueAt(1, pos);

    QObject::connect(&button,&QPushButton::clicked,[&]{
        animation.start();
    });
    a.exec();
    return 0;
}

本例中,动画将在2.4秒内将按钮移动到偏移位置,然后在剩下的0.6秒内将其移动回原来的位置。运动将在这些点之间被线性插值。

还可以对未声明为Qt属性的QObject的值进行动画处理。唯一的要求是这个值有一个setter。然后可以创建包含该值的类的子类,并声明使用此setter的属性。注意,每个Qt属性都需要一个getter,所以如果没有定义它,您需要自己提供一个getter。

 class MyGraphicsRectItem : public QObject, public QGraphicsRectItem
 {
     Q_OBJECT
     Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)
 };

上面的代码示例中,我们子类化qgraphics整流并定义一个几何属性。现在,即使qgraphics整流tem没有提供几何属性,我们也可以对小部件的几何形状进行动画处理。

关Qt属性系统的一般介绍,请参见概述。

五、动画和图形视图框架

为QGraphicsItems设置动画时,还可以使用QPropertyAnimation。 但是,QGraphicsItem不继承QObject。 一个好的解决方案是将您要设置动画的图形项目子类化。 然后,此类还将继承QObject。 这样,QPropertyAnimation可以用于QGraphicsItems。 下面的示例显示了如何完成此操作。 另一种可能性是继承QGraphicsWidget,它已经是QObject。

 class Pixmap : public QObject, public QGraphicsPixmapItem
 {
     Q_OBJECT
     Q_PROPERTY(QPointF pos READ pos WRITE setPos)
     ...

如前一节所述,我们需要定义希望设置动画的属性。
请注意,因为元对象系统需要这样做,所以QObject必须是继承的第一类。

六、缓速曲线

前所述,QPropertyAnimation在开始属性值和结束属性值之间执行一个插值。除了向动画中添加更多的键值之外,还可以使用缓动曲线。缓速曲线描述了一个控制0到1之间的插值速度的函数,如果您想在不改变插值路径的情况下控制动画的速度,那么缓速曲线非常有用。

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton button("Animated Button");
    button.show();

    QPropertyAnimation animation(&button, "pos");
    animation.setDuration(3000);
    QPoint pos = button.pos();
    animation.setKeyValueAt(0, pos);
    animation.setKeyValueAt(1, pos + QPoint(100,100));
//    animation.setKeyValueAt(1, pos);
    animation.setEasingCurve(QEasingCurve::OutBounce);

    QObject::connect(&button,&QPushButton::clicked,[&]{
        animation.start();
    });
    a.exec();
    return 0;
}

这里的动画将会遵循一条曲线,使它像一个球一样反弹,就好像它是从开始到结束的位置。QEasingCurve有大量的曲线供你选择。它们由QEasingCurve::Type enum定义。如果您需要另一个曲线,您也可以自己实现一个,并注册到QEasingCurve。

七、将动画放在一起

个应用程序通常包含多个动画。 例如,您可能想同时移动多个图形项目,或者依次移动它们。

QAnimationGroup的子类(QSequentialAnimationGroup和QParallelAnimationGroup)是其他动画的容器,因此这些动画可以按顺序或并行进行动画处理。 QAnimationGroup是不对属性进行动画处理的动画示例,但是会定期收到时间更改通知。 这使它可以将这些时间更改转发到其包含的动画,从而控制何时播放其动画。

我们看一下同时使用QSequentialAnimationGroup和QParallelAnimationGroup的代码示例。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QPushButton *bonnie = new QPushButton("Bonnie",this);
    bonnie->show();

    QPushButton *clyde = new QPushButton("Clyde",this);
    clyde->show();

    QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry",this);
    // Set up anim1
    anim1->setDuration(10000);
    anim1->setStartValue(bonnie->rect());
    anim1->setEndValue(bonnie->rect().adjusted(100,300,100,300));

    QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry",this);
    // Set up anim2
    anim2->setDuration(10000);
    anim2->setStartValue(clyde->rect());
    anim2->setEndValue(clyde->rect().adjusted(300,100,300,100));

    QParallelAnimationGroup *group = new QParallelAnimationGroup(this);
    group->addAnimation(anim1);
    group->addAnimation(anim2);

    group->start();
    resize(400,400);
}

一个并行组可以同时播放多个动画。 调用其start()函数将启动其控制的所有动画。

毫无疑问,QSequentialAnimationGroup按顺序播放其动画。 前一个动画结束后,它将开始列表中的下一个动画。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QPushButton *bonnie = new QPushButton("Bonnie",this);
    bonnie->show();

    QPushButton *clyde = new QPushButton("Clyde",this);
    clyde->show();

    QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry",this);
    // Set up anim1
    anim1->setDuration(10000);
    anim1->setStartValue(bonnie->rect());
    anim1->setEndValue(bonnie->rect().adjusted(100,300,100,300));

    QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry",this);
    // Set up anim2
    anim2->setDuration(10000);
    anim2->setStartValue(clyde->rect());
    anim2->setEndValue(clyde->rect().adjusted(300,100,300,100));

    QSequentialAnimationGroup *group = new QSequentialAnimationGroup(this);
    group->addAnimation(anim1);
    group->addAnimation(anim2);

    group->start();
    resize(400,400);
}

由于动画组本身就是动画,因此可以将其添加到另一个组中。 这样,您可以构建动画的树结构,该树结构指定动画何时相对于彼此播放。

八、动画和状态

使用状态机时,我们可以使用QSignalTransition或QEventTransition类将一个或多个动画与状态之间的过渡相关联。 这些类均从QAbstractTransition派生,QAbstractTransition定义了便捷功能addAnimation(),该功能可添加过渡发生时触发的一个或多个动画。

们还可以将属性与状态相关联,而不必自己设置开始和结束值。 下面是一个完整的代码示例,该示例对QPushButton的几何图形进行动画处理。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QPushButton *button = new QPushButton("Animated Button",this);
    button->show();

    QStateMachine *machine = new QStateMachine(this);

    QState *state1 = new QState(machine);
    state1->assignProperty(button, "geometry", QRect(0, 0, 200, 30));
    machine->setInitialState(state1);

    QState *state2 = new QState(machine);
    state2->assignProperty(button, "geometry", QRect(250, 250, 200, 30));

    QSignalTransition *transition1 = state1->addTransition(button,&QPushButton::clicked, state2);
    transition1->addAnimation(new QPropertyAnimation(button, "geometry"));

    QSignalTransition *transition2 = state2->addTransition(button,&QPushButton::clicked, state1);
    transition2->addAnimation(new QPropertyAnimation(button, "geometry"));

    machine->start();
    resize(500,300);
}

有关如何使用状态机框架进行动画制作的更全面的示例,请参见状态示例(它位于examples / animation / states目录中)。

九、总结

这篇可以应该是动画框架的大纲,应用动画框架很简单,随时都可以插入动画代码。另外本文还穿插了状态机框架的内容,可以两者对比,理解各自的优势是啥。

当前属性动画类才是用的比较多的动画类,只要看动画框架的文章就能懂得使用,也没必要去深究动画框架里的其他类,翻译其他的文档是为了动画框架的完整性。^ _ ^!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值