Qt QProperty布局中小部件的动画-小部件抖动

Qt QPropertyAnimation for widgets in layout - widgets shaking

本文关键字:小部 动画 抖动 QProperty Qt 布局      更新时间:2023-10-16

当小部件用QVboxLayout排列时,我想用QPropertyAnimation增加小部件的高度。问题是,当我打开多个小部件时,它们会在动画过程中开始移动/抖动。

我已经准备了最低限度的工作示例,这里是tar gz项目当你按下第一个、第二个、第三个小部件的"打开"按钮时,问题就会出现,然后你可以看到它们在"打开"动画过程中轻微上下移动。

有人知道该怎么做才能避免这种情况吗?我可以在主布局上设置setSizeConstraint(QLayout::SetFixedSize),它们不会抖动,但调整大小和其他操作都不起作用。

致以最诚挚的问候

Marek

不久前,我写了一个布局,它为它所包含的小部件位置设置了动画。你应该以这样的方式构建你的布局,即每个应该动画的小部件都应该在这个布局中(每个应该动画化的小部件一个AnimLayout):

#include <QLayout>
QT_FORWARD_DECLARE_CLASS(QPropertyAnimation)
class AnimLayout : public QLayout
{
Q_OBJECT
Q_PROPERTY(QPoint delta
READ delta
WRITE setDelta
NOTIFY deltaChanged)
Q_PROPERTY(QRect widgetRect
READ widgetRect
WRITE setWidgetRect
NOTIFY widgetRectChanged)
Q_PROPERTY(bool active
READ isDeltaActive
WRITE setDeltaActive
NOTIFY deltaActiveChanged)
public:
explicit AnimLayout(QWidget *parent = 0);
~AnimLayout();
QPoint delta() const;
void setDelta(const QPoint &value);
QSize sizeHint() const;
void setGeometry(const QRect &);
QSize minimumSize() const;
int count() const;
QSize deltaSize() const;
QRect widgetRect() const;
void setWidgetRect(const QRect &value);
bool isDeltaActive() const;
void setDeltaActive(bool active = true);
void updateItemPosition();
private:
void addItem(QLayoutItem *item);
QLayoutItem *itemAt(int index) const;
QLayoutItem *takeAt(int index);
signals:
void deltaChanged(const QPoint &value);
void widgetRectChanged(const QRect &value);
void deltaActiveChanged(bool active);
public slots:
void testIt();
private:
QLayoutItem *item;
QPropertyAnimation *animation;
QPoint mDelta;
bool mDeltaActive;
};
///////////////////////////////////////////////////////////
#include "animlayout.h"
#include <QPropertyAnimation>
AnimLayout::AnimLayout(QWidget *parent) :
QLayout(parent) ,
item(0)
{
animation = new QPropertyAnimation(this);
animation->setPropertyName("widgetRect");
animation->setDuration(400);
animation->setTargetObject(this);
mDeltaActive = false;
}
AnimLayout::~AnimLayout()
{
delete item;
}
QPoint AnimLayout::delta() const
{
return mDelta;
}
void AnimLayout::setDelta(const QPoint &value)
{
if (mDelta != value) {
mDelta = value;
emit deltaChanged(mDelta);
invalidate();
}
}
void AnimLayout::addItem(QLayoutItem *newItem)
{
Q_ASSERT(!item);
animation->stop();
item =newItem;
emit widgetRectChanged(item->geometry());
invalidate();
}
QSize AnimLayout::sizeHint() const
{
if (!item)
return QSize();
QSize result(item->sizeHint());
result += deltaSize();
int m = 2*margin();
result += QSize(m,m);
return result;
}
void AnimLayout::updateItemPosition()
{
QRect dest = contentsRect();
QPoint d = delta();
if (isDeltaActive()) {
d = -d;
}
if (d.x()!=0) {
if (d.x()>0) {
dest.setLeft(dest.left()+d.x());
} else {
dest.setRight(dest.right()+d.x());
}
}
if (d.y()) {
if (d.y()>0) {
dest.setTop(dest.top()+d.y());
} else {
dest.setBottom(dest.bottom()+d.y());
}
}
animation->setEndValue(dest);
if (widgetRect()!=dest) {
animation->start();
}
}
void AnimLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
updateItemPosition();
}
QLayoutItem *AnimLayout::itemAt(int i) const
{
return i==0?item:0;
}
QLayoutItem *AnimLayout::takeAt(int i)
{
Q_ASSERT(i==0);
QLayoutItem *r = item;
item = 0;
return r;
}
void AnimLayout::testIt()
{
setDeltaActive(!isDeltaActive());
}
QRect AnimLayout::widgetRect() const
{
if (item)
return item->geometry();
return QRect();
}
void AnimLayout::setWidgetRect(const QRect &value)
{
if (item && item->geometry()!=value) {
item->setGeometry(value);
emit widgetRectChanged(item->geometry());
}
}
bool AnimLayout::isDeltaActive() const
{
return mDeltaActive;
}
void AnimLayout::setDeltaActive(bool active)
{
if (active!=mDeltaActive) {
mDeltaActive = active;
animation->stop();
updateItemPosition();
emit deltaActiveChanged(active);
}
}
QSize AnimLayout::minimumSize() const
{
QSize result(deltaSize());
if (item) {
result += item->minimumSize();
}
int m = 2*margin();
result += QSize(m,m);
return result;
}
int AnimLayout::count() const
{
return item?1:0;
}
QSize AnimLayout::deltaSize() const
{
return QSize(qAbs(mDelta.x()), qAbs(mDelta.y()));
}

它有一些您不需要的额外功能(mDelta)。

很抱歉花了这么长时间;)我已经测试过了,效果很好。

然而,当我使用我以前的代码时,我已经使它不受影响地工作了。我所做的更改是将QWidget添加到QScrollArea中,然后在该小部件上设置QVBoxLayout。无论如何,非常感谢你的帮助。下面是一个main.cpp中的示例,其中有一个变量"animatedLayout",用于打开或关闭AnimLayout。

#include#包括class AnimLayout:公共QLayout{Q_OBJECTQ_PROPERTY(QRect小部件Rect读取小工具Rect写入setWidgetRectNOTIFY小工具RectChanged)公共:显式AnimLayout(QWidget*parent=0);~AnimLayout();QSize sizeHint()常量;void setGeometry(const QRect&);QSize minimumSize()const;int count()const;QRect小部件Rect()常量;void setWidgetRect(const QRect&value);void updateItemPosition();私人:void addItem(QLayoutItem*item);QLayoutItem*itemAt(int index)const;QLayoutItem*takeAt(int index);信号:void widgetRectChanged(const QRect&value);公共插槽:私人:QLayoutItem*项目;QPropertyAnimation*动画;};struct FrameDataStruct{QFrame*mainFrame;QFrame*upFrame;QFrame*downFrame;QPushButton*按钮;QVBoxLayout*upFrameLayout;QLabel*文本;QVBoxLayout*向下FrameLayout;QVBoxLayout*frameLayout;QPropertyAnimation*动画;int frame_id;int basic_height;布尔展开;AnimLayout*AnimLayout;};类Proptest:公共QMainWindow{Q_OBJECT公共:显式Proptest();~道具测试();专用插槽:void setDataStruct();void startAnimation(int frame_id);void animFinished(int frame_id);私人:QMap frameMap;QSignalMapper*animStartMapper;QSignalMapper*animFinishedMapper;bool已初始化;QWidget*scrollWidget;QVBoxLayout*main_layout;QWidget*小部件;QScrollArea*scrollArea;QVBoxLayout*中心布局;布尔布局动画;};Proptest::Proptest():小部件(新QWidget){setCentralWidget(小部件);this->setGeometry(200200300600);central_layout=新的QVBoxLayout(小部件);scrollArea=新的QScrollArea(小部件);central_layout->addWidget(滚动区域);animStartMapper=新的QSignalMapper(this);connect(animStartMapper,SIGNAL(已映射(int)),this,SLOT(startAnimation(int);animFinishedMapper=新的QSignalMapper(this);connect(animFinishedMapper,SIGNAL(已映射(int)),this,SLOT(animFinished(int);scrollWidget=新的QWidget(小部件);scrollArea->setWidget(scrollWidget);main_layout=新建QVBoxLayout(scrollWidget);main_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);scrollArea->setWidgetResizable(true);scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);layoutAnimated=true;this->setDataSetruct();}void Proptest::setDataConstruct(){for(int i=0;iexpanded=false;r->frame_id=i;r->mainFrame=新的QFrame(scrollWidget);r->upFrame=新的QFrame(r->mainFrame);r->upFrame->setMinimumHeight(40);r->button=新的QPushButton(QString("open"),r->upFrame);r->upFrameLayout=新的QVBoxLayout(r->upFrame);r->upFrameLayout->addWidget(r->按钮);r->downFrame=新的QFrame(r->mainFrame);r->text=new QLabel(QString("some-text some-text"),r->downFrame);r->downFrameLayout=新的QVBoxLayout(r->downPrame);r->downFrameLayout->addWidget(r->text);r->frameLayout=新的QVBoxLayout(r->mainFrame);r->frameLayout->addWidget(r->upFrame);r->frameLayout->addItem(新的QSpacerItem(10,10));r->frameLayout->addWidget(r->downFrame);r->frameLayout->setStretch(0,0);r->frameLayout->setStretch(1,1);r->frameLayout->setStretch(2,0);r->downFrame->setVisible(false);r->animation=new QPropertyAnimation(r->mainFrame,"minimumHeight");r->animation->setDuration(500);connect(r->按钮,SIGNAL(点击(bool)),animStartMapper,SLOT(map()));animStartMapper->setMapping(r->按钮,r->frame_id);connect(r->动画,SIGNAL(finished()),animFinishedMapper,SLOT(map()));animFinishedMapper->setMapping(r->animation,r->frame_id);if(布局动画){r->animLayout=new AnimLLayout();r->animLayout->addWidget(r->mainFrame);main_layout->addItem(r->animLayout);}其他{main_layout->addWidget(r->mainFrame);}frameMap.insert(r->frame_id,r);}main_layout->addItem(新的QSpacerItem(10,10,QSizePolicy::Minimum,QSizePolicy::Expanding));main_layout->setStretch(main_layout->count()-1,1);}void Proptest::startAnimation(int frame_id){FrameDataConstruct*r=frameMap[frame_id];if(r->展开){r->expanded=false;if(布局动画){r->downFrame->hide();}其他{r->downFrame->setVisible(false);r->animation->setStartValue(r->mainFrame->geometry().height());r->animation->setEndValue(r->basic_height);}}其他{r->expanded=true;if(布局动画){r->downFrame->show();}其他{r->basic_height=r->mainFrame->geometry().height();r->animation->setStartValue(r->basic_height);r->animation->setEndValue(r->basic_height*2);r->upFrame->setMinimumHeight(r->upFrame->height());}}if(!layout动画)r->animation->start();}void Proptest::animFinished(int frame_id){FrameDataConstruct*r=frameMap[frame_id];if(r->展开)r->downFrame->setVisible(true);}Proptest::~ Proptest(){}动画布局::动画布局(QWidget*父级):QLayout(父级),项(0){animation=新的QPropertyAnimation(this);animation->setPropertyName("widgetRect");animation->setDuration(400);animation->setTargetObject(this);}动画布局::~动画布局(){删除项目;}void AnimLayout::addItem(QLayoutItem*newItem){Q_ASSERT(!项);animation->stop();item=newItem;emit widgetRectChanged(item->geometry());invalidate();}QSize动画布局::sizeHint()常量{if(!item)return QSize();QSize结果(item->sizeHint());int m=2*margin();结果+=Q大小(m,m);返回结果;}void动画布局::updateItemPosition(){QRect dest=contentsRev();animation->setEndValue(dest);if(widgetRect()=dest){animation->start();}}void AnimLayout::setGeometry(const QRect&rect){Q布局::setGeometry(rect);updateItemPosition();}QLayoutItem*AnimLayout::itemAt(int i)const{是否返回i==0?项目:0;}QLayoutItem*AnimLayout::takeAt(int i){Q_ASSERT(i==0);QLayoutItem*r=项目;item=0;返回r;}QRect动画布局::widgetRect()常量{if(item)return item->geometry();return QRect();}void AnimLayout::setWidgetRect(const QRect和value){if(item&&item->geometry()=值){item->setGeometry(值);emit widgetRectChanged(item->geometry());}}QSize动画布局::minimumSize()const{QSize结果(item->minimumSize());int m=2*margin();结果+=Q大小(m,m);返回结果;}int AnimLayout::count()常量{退货项目?1:0;}int main(int argc,char*argv[]){Q应用程序a(argc,argv);Proptest w;w.show();返回a.exec();}#包括"main.moc">

最诚挚的问候

Marek

我的答案是,不知道为什么,但previou被删除了。

我用过你的动画布局,效果很好。下面是main.cpp中的一个示例,该示例使用"layoutAnimated"变量来打开和关闭AnimLayout。

#include <QApplication>
#include <QtWidgets>
class AnimLayout : public QLayout
{
Q_OBJECT
Q_PROPERTY(QRect widgetRect
READ widgetRect
WRITE setWidgetRect
NOTIFY widgetRectChanged)
public:
explicit AnimLayout(QWidget *parent = 0);
~AnimLayout();
QSize sizeHint() const;
void setGeometry(const QRect &);
QSize minimumSize() const;
int count() const;
QRect widgetRect() const;
void setWidgetRect(const QRect &value);
void updateItemPosition();
private:
void addItem(QLayoutItem *item);
QLayoutItem *itemAt(int index) const;
QLayoutItem *takeAt(int index);
signals:
void widgetRectChanged(const QRect &value);
public slots:
private:
QLayoutItem *item;
QPropertyAnimation *animation;
};
struct FrameDataStruct {
QFrame      *mainFrame;
QFrame      *upFrame;
QFrame      *downFrame;
QPushButton *button;
QVBoxLayout *upFrameLayout;
QLabel      *text;
QVBoxLayout *downFrameLayout;
QVBoxLayout *frameLayout;
QPropertyAnimation  *animation;
int         frame_id;
int         basic_height;
bool        expanded;
AnimLayout  *animLayout;
};
class Proptest : public QMainWindow
{
Q_OBJECT
public:
explicit Proptest();
~Proptest();
private slots:
void setDataStruct();
void startAnimation(int frame_id);
void animFinished(int frame_id);
private:
QMap<int,FrameDataStruct*> frameMap;
QSignalMapper   *animStartMapper;
QSignalMapper   *animFinishedMapper;
bool            initialized;
QWidget         *scrollWidget;
QVBoxLayout     *main_layout;
QWidget         *widget;
QScrollArea     *scrollArea;
QVBoxLayout     *central_layout;
bool            layoutAnimated;
};
Proptest::Proptest()
: widget(new QWidget)
{
setCentralWidget(widget);
this->setGeometry(200,200,300,600);
central_layout=new QVBoxLayout(widget);
scrollArea=new QScrollArea(widget);
central_layout->addWidget(scrollArea);

animStartMapper=new QSignalMapper(this);
connect(animStartMapper,SIGNAL(mapped(int)),this,SLOT(startAnimation(int)));
animFinishedMapper=new QSignalMapper(this);
connect(animFinishedMapper,SIGNAL(mapped(int)),this,SLOT(animFinished(int)));
scrollWidget=new QWidget(widget);
scrollArea->setWidget(scrollWidget);
main_layout=new QVBoxLayout(scrollWidget);
main_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
layoutAnimated=true;
this->setDataStruct();
}
void Proptest::setDataStruct() {
for(int i=0;i<5;i++) {
FrameDataStruct *r=new FrameDataStruct;
r->expanded=false;
r->frame_id=i;
r->mainFrame=new QFrame(scrollWidget);
r->upFrame=new QFrame(r->mainFrame);
r->upFrame->setMinimumHeight(40);
r->button=new QPushButton(QString("open"),r->upFrame);
r->upFrameLayout=new QVBoxLayout(r->upFrame);
r->upFrameLayout->addWidget(r->button);
r->downFrame=new QFrame(r->mainFrame);
r->text=new QLabel(QString("some text SOME TEXT some text"),r->downFrame);
r->downFrameLayout=new QVBoxLayout(r->downFrame);
r->downFrameLayout->addWidget(r->text);
r->frameLayout=new QVBoxLayout(r->mainFrame);
r->frameLayout->addWidget(r->upFrame);
r->frameLayout->addItem(new QSpacerItem(10,10));
r->frameLayout->addWidget(r->downFrame);
r->frameLayout->setStretch(0,0);
r->frameLayout->setStretch(1,1);
r->frameLayout->setStretch(2,0);
r->downFrame->setVisible(false);
r->animation=new QPropertyAnimation(r->mainFrame,"minimumHeight");
r->animation->setDuration(500);
connect(r->button,SIGNAL(clicked(bool)),animStartMapper,SLOT(map()));
animStartMapper->setMapping(r->button,r->frame_id);
connect(r->animation,SIGNAL(finished()),animFinishedMapper,SLOT(map()));
animFinishedMapper->setMapping(r->animation,r->frame_id);

if(layoutAnimated) {
r->animLayout=new AnimLayout();
r->animLayout->addWidget(r->mainFrame);
main_layout->addItem(r->animLayout);
}
else {
main_layout->addWidget(r->mainFrame);
}
frameMap.insert(r->frame_id,r);
}
main_layout->addItem(new QSpacerItem(10,10,QSizePolicy::Minimum,QSizePolicy::Expanding));
main_layout->setStretch(main_layout->count()-1,1);
}
void Proptest::startAnimation(int frame_id) {
FrameDataStruct *r=frameMap[frame_id];
if(r->expanded) {
r->expanded=false;
if(layoutAnimated) {
r->downFrame->hide();
}
else {
r->downFrame->setVisible(false);
r->animation->setStartValue(r->mainFrame->geometry().height());
r->animation->setEndValue(r->basic_height);
}
} else {
r->expanded=true;
if(layoutAnimated) {
r->downFrame->show();
}
else {
r->basic_height=r->mainFrame->geometry().height();
r->animation->setStartValue(r->basic_height);
r->animation->setEndValue(r->basic_height*2);
r->upFrame->setMinimumHeight(r->upFrame->height());
}
}
if(!layoutAnimated)
r->animation->start();
}
void Proptest::animFinished(int frame_id) {
FrameDataStruct *r=frameMap[frame_id];
if(r->expanded)
r->downFrame->setVisible(true);
}
Proptest::~Proptest() {
}
AnimLayout::AnimLayout(QWidget *parent) :
QLayout(parent) ,
item(0)
{
animation = new QPropertyAnimation(this);
animation->setPropertyName("widgetRect");
animation->setDuration(400);
animation->setTargetObject(this);
}
AnimLayout::~AnimLayout()
{
delete item;
}
void AnimLayout::addItem(QLayoutItem *newItem)
{
Q_ASSERT(!item);
animation->stop();
item =newItem;
emit widgetRectChanged(item->geometry());
invalidate();
}
QSize AnimLayout::sizeHint() const
{
if (!item)
return QSize();
QSize result(item->sizeHint());
int m = 2*margin();
result += QSize(m,m);
return result;
}
void AnimLayout::updateItemPosition()
{
QRect dest = contentsRect();
animation->setEndValue(dest);
if (widgetRect()!=dest) {
animation->start();
}
}
void AnimLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
updateItemPosition();
}
QLayoutItem *AnimLayout::itemAt(int i) const
{
return i==0?item:0;
}
QLayoutItem *AnimLayout::takeAt(int i)
{
Q_ASSERT(i==0);
QLayoutItem *r = item;
item = 0;
return r;
}
QRect AnimLayout::widgetRect() const
{
if (item)
return item->geometry();
return QRect();
}
void AnimLayout::setWidgetRect(const QRect &value)
{
if (item && item->geometry()!=value) {
item->setGeometry(value);
emit widgetRectChanged(item->geometry());
}
}
QSize AnimLayout::minimumSize() const
{
QSize result(item->minimumSize());
int m = 2*margin();
result += QSize(m,m);
return result;
}
int AnimLayout::count() const
{
return item?1:0;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Proptest w;
w.show();
return a.exec();
}
#include "main.moc"

致以最诚挚的问候

Marek