简体   繁体   中英

Qt QPropertyAnimation for widgets in layout - widgets shaking

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.

I have prepared minimum working example, here tar gz project The problem 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.

Has someone idea what to do to avoid this ? I can set setSizeConstraint(QLayout::SetFixedSize) on main layout and they dont shake, but then resizing and other doesn't work.

Best Regards

Marek

Some time ago I've wrote a layout which animates 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 ).

Sorry it took me so long ;) I have tested it and it works great.

However, when I was working with my previous code I have made it work without shaking. The change I made was to add QWidget into QScrollArea and then set QVBoxLayout on that widget. Anyway many thanks for help. Below there is example in one main.cpp and there is variable "animatedLayout" which turns on or off your AnimLayout.

#include 
    #include 

    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 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;iexpanded=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"

Best Regards

Marek

My answer again, dont know why but previosu was deleted.

I have used Your AnimLayout and it works great. Below there is example in main.cpp with "layoutAnimated" variable to switch on and 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"

Best Regards

Marek

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM