Reputation: 320
I want to make widgets increase in height with QPropertyAnimation, when widgets are arranged with QVBoxLayout.
The problem is that when I open more than one widget, they start to move/shake during animation.
The issue appears when you press "open" button for first, second, then third widget, you can see then that they are shaking, moving slightly up and down during "open" animation.
How do I avoid this?
I can set setSizeConstraint(QLayout::SetFixedSize)
on main layout, and they don't shake, but then resizing, for example, doesn't work.
Upvotes: 0
Views: 2256
Reputation: 37512
Some time ago, I wrote a layout which animates the widget position it contains.
You should build your layout in such way that each widget which should be animated should be inside this layout (one AnimLayout
per widget which should be animated):
#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()));
}
It has some extra functionality you don't need (mDelta
).
Upvotes: 1
Reputation: 320
This answer works great.
However, I made it work without shaking. The change I made was to add QWidget into QScrollArea, and then set QVBoxLayout on that widget.
Below is an example where animatedLayout
turns on/off 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"
Upvotes: -1