使用拉伸时动画 QVBoxLayout factors/when 最终尺寸未知
Animating a QVBoxLayout when using stretch factors/when the final size is unknown
这个 post 与 this question about animating a QVBoxLayout 密切相关,当我找到它时让我中途回来了。
我有一个包含一些小部件的 QVBoxLayout。最后一个只在某些情况下可见,所以我想在它出现和消失时对其进行动画处理。
我目前正在使用 QPropertyAnimation
为我想要显示和隐藏的小部件的 maximumHeight
属性 设置动画。但是,这里的问题是我不知道小部件的大小应该是多少:QVBoxLayout
根据父 window 大小和拉伸因素决定。
因此,我不知道应该给 QPropertyAnimation::setStartValue
和 setEndValue
什么值来显示动画(隐藏时,只需使用当前高度就可以了)。
它有点像简单地给出一个非常大的值(例如 2000 像素,当你期望它很少大于 400 时),因为我们正在动画化 maximum 高度,但这有问题。即,finished
信号在 完整 动画持续时间之后发出,即使动画很久以前(视觉上)停止,因为小部件达到其分配的大小。
隐藏时,问题反而是动画延迟:从 2000 px 到当前 height()
,没有任何反应;之后,它迅速崩溃。
然后我考虑为拉伸因子设置动画,但找不到方法,因为它们没有作为属性公开。
作为最后的手段,可能可以对布局进行子类化并创建一个 属性 来调整最后两项的拉伸系数,但这在几个方面感觉像是一个巨大的 hack。
我怎样才能用漂亮的方式制作动画?
以防万一有人要代码示例,我编写了一个简单的测试应用程序。如果你不打算问,你可以跳过它。
#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();
}
我最终选择了子类化路线,因为我想不出别的,而且这个问题没有看到很多activity。
子类(必须在头文件中否则会出现 undefined reference to 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();
}
我会再等一天左右接受这个作为答案,以防万一有人提出一些不那么骇人听闻的东西(例如根据拉伸因子计算确切最终尺寸的方法)。
这个 post 与 this question about animating a QVBoxLayout 密切相关,当我找到它时让我中途回来了。
我有一个包含一些小部件的 QVBoxLayout。最后一个只在某些情况下可见,所以我想在它出现和消失时对其进行动画处理。
我目前正在使用 QPropertyAnimation
为我想要显示和隐藏的小部件的 maximumHeight
属性 设置动画。但是,这里的问题是我不知道小部件的大小应该是多少:QVBoxLayout
根据父 window 大小和拉伸因素决定。
因此,我不知道应该给 QPropertyAnimation::setStartValue
和 setEndValue
什么值来显示动画(隐藏时,只需使用当前高度就可以了)。
它有点像简单地给出一个非常大的值(例如 2000 像素,当你期望它很少大于 400 时),因为我们正在动画化 maximum 高度,但这有问题。即,finished
信号在 完整 动画持续时间之后发出,即使动画很久以前(视觉上)停止,因为小部件达到其分配的大小。
隐藏时,问题反而是动画延迟:从 2000 px 到当前 height()
,没有任何反应;之后,它迅速崩溃。
然后我考虑为拉伸因子设置动画,但找不到方法,因为它们没有作为属性公开。
作为最后的手段,可能可以对布局进行子类化并创建一个 属性 来调整最后两项的拉伸系数,但这在几个方面感觉像是一个巨大的 hack。
我怎样才能用漂亮的方式制作动画?
以防万一有人要代码示例,我编写了一个简单的测试应用程序。如果你不打算问,你可以跳过它。
#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();
}
我最终选择了子类化路线,因为我想不出别的,而且这个问题没有看到很多activity。
子类(必须在头文件中否则会出现 undefined reference to 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();
}
我会再等一天左右接受这个作为答案,以防万一有人提出一些不那么骇人听闻的东西(例如根据拉伸因子计算确切最终尺寸的方法)。