使用拉伸因子/当最终大小未知时对 QVBoxLayout 进行动画处理



这篇文章与这个关于对QVBoxLayout进行动画处理的问题密切相关,当我找到它时,这个问题让我回到了一半。

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

简单地给出一个非常过分的值(例如 2000 像素,当您预计它很少大于 400 时)是有效的,因为我们正在对最大高度进行动画处理,但这有问题。也就是说,finished信号在整个动画持续时间之后发出,即使动画在小部件达到其分配大小时很久以前就停止了(视觉上)。
隐藏时,问题在于动画延迟:从 2000 像素到当前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();
}

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

子类(必须位于头文件中,否则您将获得对 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 开始,否则动画开始时会出现闪烁。

该解决方案感觉很笨拙,但在实践中看起来和效果都很好。
为了完整起见,以下是完整的测试程序,已更新以使用此修复程序:

主.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

主.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();
}

我会再等一天左右才能接受这个答案,以防有人提出一些不那么笨拙的东西(例如根据拉伸系数计算确切的最终尺寸的方法)。

最新更新