在使用拉伸因子/最终大小未知时动画化QVBoxLayout

时间:2015-03-09 17:12:30

标签: qt animation

这篇文章与this question about animating a QVBoxLayout密切相关,当我找到它时,它让我回到了中途。

我有一个包含几个小部件的QVBoxLayout。它们中的最后一个仅在某些情况下可见,因此我想在它出现时消失它并消失 我目前正在使用QPropertyAnimation设置我要显示和隐藏的小部件的maximumHeight属性的动画。但是,这里的问题是我不知道窗口小部件的大小:QVBoxLayout根据父窗口大小和伸展因子决定。 因此,我不知道应该给QPropertyAnimation::setStartValuesetEndValue赋予什么值,对于节目动画(隐藏时,它应该只是使用当前高度)。

它可以简单地给出一个非常大的值(例如2000 px,当你期望它很少大于400时),因为我们正在为最大高度设置动画,但这有问题。即,{em>完整动画持续时间之后发出finished信号,即使动画很久以前停止(视觉上),当小部件达到其分配的大小时。 隐藏时,问题是动画被延迟:从2000 px到当前height(),没有任何反应;之后,它迅速崩溃。

然后我考虑使用拉伸因子来制作动画,但无法找到方法,因为它们不会作为属性公开。
作为最后的手段,有可能对布局进行子类化并创建一个属性来调整最后两个项目的伸展因子,但这在几个方面感觉像是一个巨大的黑客。

我怎样才能以一种很好的方式为它设置动画?

为了防止有人要求代码示例,我写了一个简单的测试应用程序。如果你不想问,你可以跳过它来看看。

#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton>
#include <QTextEdit>
#include <QPropertyAnimation>

QTextEdit *topTextEdit = nullptr;
QTextEdit *bottomTextEdit = nullptr;

void toggleButtonClicked() {
    QPropertyAnimation *anim = new QPropertyAnimation(bottomTextEdit, 
                                                      "maximumHeight");
    anim->setDuration(1200);

    if (bottomTextEdit->isVisible()) {
        anim->setStartValue(1000);
        anim->setEndValue(0);
        QObject::connect(anim, &QPropertyAnimation::finished, [] {
            bottomTextEdit->hide();
            topTextEdit->append("Animation finished");
        });
    }
    else {
        bottomTextEdit->show();
        anim->setStartValue(0);
        anim->setEndValue(1000);
        QObject::connect(anim, &QPropertyAnimation::finished, [] {
            topTextEdit->append("Animation finished");
        });
    }

    anim->start(QAbstractAnimation::DeleteWhenStopped);
    topTextEdit->append("Animation started");
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    topTextEdit = new QTextEdit;
    bottomTextEdit = new QTextEdit;

    QPushButton *toggleButton = new QPushButton("Toggle");
    QObject::connect(toggleButton, &QPushButton::released,
                     &toggleButtonClicked);

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(toggleButton);
    vbox->addWidget(topTextEdit, 10);
    vbox->addWidget(bottomTextEdit, 6);

    QWidget *widget = new QWidget;
    widget->setLayout(vbox);

    bottomTextEdit->setMaximumHeight(0);
    bottomTextEdit->hide();
    widget->show();

    return a.exec();
}

1 个答案:

答案 0 :(得分:1)

我最终选择了子类化路线,因为我无法想到其他任何事情,而且这个问题没有看到很多活动。

子类(必须位于头文件中,否则您将获得对vtable错误的未定义引用):

class EVBoxLayout : public QVBoxLayout {
    Q_OBJECT
    Q_PROPERTY(int lastStretch READ lastStretch WRITE setLastStretch)

public:
    EVBoxLayout() {}
    int lastStretch() const { return this->stretch(this->count() - 1); }
    void setLastStretch(int newStretch) {
        this->setStretch(this->count() - 1, newStretch);
    }
};

实施后,将QVBoxLayout替换为EVBoxLayout,然后使用

创建动画
QPropertyAnimation *anim = new QPropertyAnimation(vbox, "lastStretch");

要考虑这一点,需要考虑以下几点:

  • 为了让动画更流畅,你应该使用非常大的伸展因子;因为它们是整数,所以它们是例如动画。 1到5将非常不稳定。我使用了1000:600的比例,所以我的动画从底部小部件的1到600,我希望它是较小的。
  • 确保将窗口小部件的最小高度设置为1(不是0!),否则动画可能会从中途开始。
  • 确保从1 而不是0来设置拉伸因子的动画,或者在动画开始时会出现闪烁。

解决方案感觉很骇人,但它在实践中看起来很有效 为了完整起见,这里是完整的测试程序,已更新为使用此修复程序:

main.h:

#ifndef MAIN_H
#define MAIN_H

#include <QVBoxLayout>

class EVBoxLayout : public QVBoxLayout {
    Q_OBJECT
    Q_PROPERTY(int lastStretch READ lastStretch WRITE setLastStretch)

public:
    int lastStretch() const { return this->stretch(this->count() - 1); }
    void setLastStretch(int newStretch) { this->setStretch(this->count() - 1, newStretch); }
};

#endif // MAIN_H

main.cpp中:

#include "main.h"

#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton>
#include <QTextEdit>
#include <QPropertyAnimation>

QTextEdit *topTextEdit = nullptr;
QTextEdit *bottomTextEdit = nullptr;   
EVBoxLayout *vbox = nullptr;

void toggleButtonClicked() {
    QPropertyAnimation *anim = new QPropertyAnimation(vbox, "lastStretch");
    anim->setDuration(250);

    // Without this, the scrollbar may appear (and then disappear again)
    // during animation.
    bottomTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    if (bottomTextEdit->isVisible()) {
        anim->setStartValue(600);
        anim->setEndValue(1);
        QObject::connect(anim, &QPropertyAnimation::finished, [] {
            bottomTextEdit->hide();
            topTextEdit->append("Animation finished");
            bottomTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        });
    }
    else {
        bottomTextEdit->show();
        anim->setStartValue(1);
        anim->setEndValue(600);
        QObject::connect(anim, &QPropertyAnimation::finished, [] {
            topTextEdit->append("Animation finished");
            bottomTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        });
    }

    anim->start(QAbstractAnimation::DeleteWhenStopped);
    topTextEdit->append("Animation started");
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    topTextEdit = new QTextEdit;
    bottomTextEdit = new QTextEdit;
    bottomTextEdit->setMinimumHeight(1);

    QPushButton *toggleButton = new QPushButton("Toggle");
    QObject::connect(toggleButton, &QPushButton::released,
                     &toggleButtonClicked);

    vbox = new EVBoxLayout;
    vbox->addWidget(toggleButton);
    vbox->addWidget(topTextEdit, 1000);
    vbox->addWidget(bottomTextEdit, 1);

    QWidget *widget = new QWidget;
    widget->setLayout(vbox);

    bottomTextEdit->hide();
    widget->show();

    return a.exec();
}

我会等待另一天左右接受这个作为答案,以防有人带来一些不那么粗暴的东西(例如从拉伸因子计算确切最终尺寸的方法)。