Qt属性动画效果的实现QPropertyAnimation & 自定义属性Q_PROPERTY

原文链接:https://www.cnblogs.com/lvdongjie/p/4366092.html

     https://blog.csdn.net/wzs250969969/article/details/78418124

     https://blog.csdn.net/kaida1234/article/details/82896611

 

Qt动画架构图:

动画框架由基类QAbstractAnimation和它的两个儿子QVariantAnimation和QAnimationGroup组成。QAbstractAnimation是所有动画类的祖宗。它包含了所有动画的基本属性。比如开始,停止和暂停一个动画的能力。它也可以接收时间改变通知。

动画框架又进一步提供了QProertyAnimation类。它继承自QVariantAnimation并对某个Qt属性(它须是Qt的”元数据对象系统”的一部分,见http://blog.csdn.net/nkmnkm/article/details/8225089)执行动画。此类对属性执行一个宽松曲线插值。所以当你想去动画一个值时,你可以把它声明为一个属性,并且让你的类成为一个QObject。这给予我们极大的自由度来动画那些已存在的widget和其它QObject。

复杂的动画可以通过建立一个QAbstractAnimation的树来构建。这个树通过使用QAnimationGroups来创建,QAnimationGroups作为其它动画的容器。注意动画组也是从QAbstractAnimation派生的,所以动画组可以再包含其它动画组。

动画框架可以单独使用,同时也被设计为状态机框架的一部分。状态机提供了一个特定的状态可以用来播放动画。在进入或退出某个状态时QState也可以设置属性们,并且这个特定的动画状态将在指定QPropertyAnimation时给予的值之间做插值运算。后面我们要进一步介绍此问题。

在场景的背后,动画被一个全局定时器收集,这个定时器发送update到所有的正在播放的动画中。

 

  • 动画框架中的类们

QAbstractAnimation 所有动画类的基类   

QAnimationGroup 动画组的基类   

QEasingCurve 控制动画的宽松曲线类   

QParallelAnimationGroup 并行动画组类  

QPauseAnimation 串行动画组类的暂停类  

QPropertyAnimation 动画Qt属性的类 

QSequentialAnimationGroup 串行动画组类   

QTimeLine 控制动画的时间线类   

QVariantAnimation 各动画类的虚基类

 

 

QPropertyAnimation 类就是实现属性以动画形式改变的类,示例如下:

 1、我们选择动画Qt属性的一个主要理由是Qt属性为我们提供了自己动画已存在的类的自由度。尤其是QWidget类(我们也可以把它嵌入到一个QGraphicsView中)具有很多属性表示其bounds,colors等等。让我们看一个小例子:

QPushButton button("Animated Button");  
button.show();  
QPropertyAnimation animation(&button, "geometry");  //geometry就是说明要对这个属性以动画形式变化
animation.setDuration(10000);  
animation.setStartValue(QRect(0, 0, 0, 0));  
animation.setEndValue(QRect(250, 250, 100, 30));  
animation.start();  

这段代码将把按钮在10秒种内从屏幕的左上角移动到(250,250)处,而且是逐渐变大。见下图效果:

2、上面的例子举在开始值和结束值之间做线性插值。还可以在开始和结束值之间设置值,插值运算就会经过这些点。

animation1 = new QPropertyAnimation(ui.pushButton, "geometry");   
animation1->setDuration(10000);  
animation1->setKeyValueAt(0, QRect(0, 0, 00, 00));  
animation1->setKeyValueAt(0.4, QRect(20, 250, 20, 30));  
animation1->setKeyValueAt(0.8, QRect(100, 250, 20, 30));  
animation1->setKeyValueAt(1, QRect(250, 250, 100, 30));  
animation1->setEndValue(QRect(250, 250, 100, 30));  

在此例中,动画将按钮在8秒中内弄到(250,250)处,然后在2秒种内又弄回原位。移位是在这些点中间以线性插值进行的。

 

3、你也有可能动画一个QObject的值,虽然这些值并没有被声明为Qt属性。唯一的要求就是这个值具有一个setter。之后你可以从这个类派生子类从而包含这些值并且声明一个使用这个setter的属性。注意每个Qt属性都需要有一个getter,所以你需要提供一个getter,如果它不存在的话。

class MyGraphicsRectItem : public QObject, public QGraphicsRectItem  
{  
    Q_OBJECT  
    Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)  //这个相当于给这个类声明了一个geometry的属性,获取这个属性值通过geometry方法,更改这个属性的值通过setGeometry方法
};  

 在上例中,我们派生了QGraphicsRectItem并定义了一个geometry属性。我们现在可以动画这个widget的geometry了,即使QGraphicsRectItem没有提供geometry属性。

4、宽松曲线

 QPropertyAnimation在属性的开始值和结束值之间执行一个插值运算。除了向动画添加更多的关键值外,你还可以使用一个宽松曲线。宽松曲线描述了一个在0和1之间插值的速度变化的函数,如果你想控制一个动画的速度而不改变插值的路径时,就非常有用。

animation1 = new QPropertyAnimation(ui.pushButton, "geometry");   
animation1->setDuration(10000);  
animation1->setStartValue(QRect(0, 0, 0, 0));  
animation1->setEndValue(QRect(250, 250, 100, 30));  
animation1->setEasingCurve(QEasingCurve::OutBounce);  

这里,动画将按照一个曲线进行,这个曲线使得动画像一个跳动的皮球从开始位置跳到结束位置。QEasingCurve具有一个大曲线集合,你可以从里面选择一个。它们被定义为QEasingCurve::Type枚举。如果你需要不一样的曲线,你也可以自己实现一个,然后注册到QEasingCurve。

5、下面我们自定义一个属性alpha(毕竟上面的geometry属性已经存在,属性对应的读写方法也存在)来实现我们的属性动画

#ifndef MAIN_WINDOW_H
#define MAIN_WINDOW_H
 
...
 
class MainWindow : public CustomWindow
{
    Q_OBJECT
    Q_PROPERTY(int alpha READ alpha WRITE setAlpha)
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
 
private:
    int alpha() const;
    void setAlpha(const int alpha);
 
private:
    int m_nAlpha;
    QLabel *m_pLabel;
};
 
#endif // MAIN_WINDOW_H
View Code
#include "main_window.h"
 
MainWindow::MainWindow(QWidget *parent)
    : CustomWindow(parent)
{
    ...
 
    QPushButton *pStartButton = new QPushButton(this);
    pStartButton->setText(QString::fromLocal8Bit("开始动画"));
 
    m_pLabel = new QLabel(this);
    m_pLabel->setText(QString::fromLocal8Bit("一去丶二三里"));
    m_pLabel->setAlignment(Qt::AlignCenter);
    m_pLabel->setStyleSheet("color: rgb(0, 160, 230);");
 
    QPropertyAnimation *pAnimation = new QPropertyAnimation();
    pAnimation->setTargetObject(this);
    pAnimation->setPropertyName("alpha");
    pAnimation->setDuration(1000);
    pAnimation->setKeyValueAt(0, 255);
    pAnimation->setKeyValueAt(0.5, 100);
    pAnimation->setKeyValueAt(1, 255);
    pAnimation->setLoopCount(-1);  //永远运行,直到stop
    connect(pStartButton, SIGNAL(clicked(bool)), pAnimation, SLOT(start()));
 
    ...
}
 
int MainWindow::alpha() const
{
    return m_nAlpha;
}
 
void MainWindow::setAlpha(const int alpha)
{
    m_nAlpha = alpha;
    QString strQSS = QString("color: rgb(0, 160, 230); background-color: rgba(10, 160, 105, %1);").arg(m_nAlpha);
    m_pLabel->setStyleSheet(strQSS);
}
View Code

 

6、接下来我们就必须介绍一下自定义属性Q_PROPERTY

/*
    Qt提供了一个绝妙的属性系统。跟那些由编译器提供的属性差不多。然而,作为一个独立于编译器和平台的库,Qt不依赖于非标准的编译特性,
   比如__property 或[property]。Qt可以在任何平台上的标准编译器下编译。Qt属性系统基于元数据对象系统--就是那个提供了对象内置信号和槽通讯机制的家伙。 Q_PROPERTY()是一个宏,用来在一个类中声明一个属性property,由于该宏是qt特有的,需要用moc进行编译,故必须继承于QObject类。
*/ Q_PROPERTY(type name READ getFunction [WRITE setFunction] [RESET resetFunction] [NOTIFY notifySignal] [DESIGNABLE bool] [SCRIPTABLE bool] [STORED bool] [USER bool] [CONSTANT] [FINAL])

//示例
Q_PROPERTY(double minValue READ getMinValue WRITE setMinValue)
Q_PROPERTY(bool animation READ getAnimation WRITE setAnimation)
Q_PROPERTY(QColor barColor READ getBarColor WRITE setBarColor)

  • 一个属性的行为就像类的数据成员,但是它还具有附加的特性,这些特性可以被元数据对象系统操作。这些特性是:
    需要一个READ访问器函数。用于读属性的值。理想情况下,有一个不变的函数用于此目的,并且它必须返回属性的类型的值或指针或引用。例如,QWidget::focus是一个只读的属性,它对应一个读函数:QWidget::hasFocus()。
  • 一个可选的WRITE访问器函数。它用于设置属性的值。它必须返回空并且至少具有一个参数,参数是属性类型的值或指针或引用。例如:QWidget::enabled具有WRITE函数QWidget::setEnable()。只读属性不需要写函数。例如,QWidget::focus没有对应的写函数。
  • 一个可选的RESET函数。用于设置属性的值到它的默认值。例如:QWidget::cursor具有典型的READ和WRITE函数,QWidget::cursor()和QWidget::setCursor(),并且它也具有一个RESET函数,QWidget::unsetCursor()。RESET函数必须返回void并且不带有任何参数。
  • 一个可选的NOTIFY信号。如果被定义了,信号将在属性的值改变时发出。信号必须带有一个参数,这个参数的类型必须与属性相同;参数保存的是属性的新值。
  • 一个DESIGNABLE变量表明此属性是否在界面设计器的属性编辑器中出现。大多数属性是可见的,除了为这个变量传入true或false,你还可以指定一个bool型的成员函数。
  • SCRIPTABLE变量表明这个属性是否可以被一个脚本引擎操作(默认是true)。你也可以赋予它true或false或bool型函数。
  • STORED变量表明了属性是否被认为是独立存在还是依赖于其它的值而存在。它也表明是否在保存对象状态时保存此属性的值。大多数属性都是需要保存的,但是,如QWidget::minimumWidth()就是不被保存的,因为它的值是从另一个属性QWidget::minimumSize()得来的。
  • USER变量表明属性是否被设计为面向用户的或用户可修改的类属性。通常,每个类只有一个USER属性。例如,QAbstractButton::checked是按钮类的用户可修改属性。注意QItemDelegate获取和设置widget的USER属性。
  • CONSTANT的出现表明属性的值是不变的。对于一个object实例,常量属性的READ方法在每次被调用时必须返回相同的值。此常量值可能在不同的object实例中不相同。一个常量属性不能具有WRITE方法或NOYIFY信号。
  • FINAL变量的出现表明属性不能被派生类所重写。有些情况下,这可以用于效率优化,但不是被moc强制的。程序员必须永远注意不能重写一个FINAL属性。

 

READ,WRITE和RESET函数都可以被继承。它们也可以是虚函数。当它们在被多重继承中被继承时,它们必须出现在第一个被继承的类中。

  属性的类型可以是被QVariant支持的所有类型,也可以是用户定义的类型。在下面的例子中,类QDate被当作用户自定义类型。

Q_PROPERTY(QDate data READ getDate WRITE setDate)

因为QDate是用户定义的,你必须包含<QDate>头文件。

    对于QMap,QList和QValueList属性,属性的值是一个QVariant,它包含整个list或map。注意Q_PROPERTY字符串不能包含逗号,因为逗号会划分宏的参数。因此,你必须使用QMap作为属性的类型而不是QMap<QString,QVariant>。为了保持一致性,也需要用QList和QValueList而不是QList<QVariant>和QValueList<QVariant>。

代码调用的例子:

class Test : public QObject {  
Q_OBJECT  
  
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)  
  
public:  
  
Test(QObject *parent = 0) : QObject(parent) {}  
  
virtual ~Test(){}  
  
void setEnabled(bool e) { enabled = e; }  
  
bool isEnabled() const { return enabled; }  
  
private:  
  
bool enabled;  
  
};  

然后在主函数中添加:

Test *test = new Test;  
  
test->setProperty("enabled", true);  
  
//test->setEnabled(true);        //ok also work  
  
if(test->property("enabled").toBool()) ..... 

Qt Creator Designer插件的 例子:

 

头文件中定义了一个minValue 的属性,如下:

class BarRuler : public QWidget
{
    Q_OBJECT    
    Q_PROPERTY(double minValue READ getMinValue WRITE setMinValue)

public:
    explicit BarRuler(QWidget *parent = 0);
    ~BarRuler();

private:    
    double minValue;

public:    
    double getMinValue()            const;

public slots:
    void setRange(double minValue, double maxValue);
};

cpp文件实现如下:

#include "barruler.h"

BarRuler::BarRuler(QWidget *parent) : QWidget(parent)
{    
    minValue = 0;
}

BarRuler::~BarRuler()
{
}

double BarRuler::getMinValue() const
{
    return this->minValue;
}

void BarRuler::setMinValue(double minValue)
{
    this->minValue = minValue;
    update();
}

在设计模式界面调用如下:

 

1、先拖入一个widget控件

2、在其上右键选择“提升为”BarRuler

3、点击属性栏的加号,选择其它类型,如图

 

 

 

posted @ 2021-08-04 16:57  kongbursi  阅读(1917)  评论(2编辑  收藏  举报