user2018761
user2018761

Reputation: 320

Widgets shaking when using QPropertyAnimation to animate them in a layout

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

Answers (2)

Marek R
Marek R

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

user2018761
user2018761

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

Related Questions