pythonpyqtpyqt4python-2.xqpropertyanimation

Qt resize layout during widget property animation


I have an existing application that I am polishing off and I want to add some animation to a few of the widgets. Animating widgets with QPropertyAnimation outside of layouts is easy and fun, however when they are in a layout I am having various difficulties. The current one giving me a headache is that when I animate the size of a widget, the layout does not adjust to it's new size.

So lets say I have a QVBoxLayout with three widgets: a label which should expand to all available space, a treeview, and a button. When I click the button I want the tree to collapse and the label to take over it's space. Below is this example in code, and as you can see while the tree animates it's size nothing happens, and then when I hide it at the end of the animation the label pops to fill the now vacant space. So it seems that during the animation the layout does not "know" the tree is resizing. What I would like to happen is that AS the tree shrinks, the label expands to fill it.

Could this could be done not by absolute sizing of the label, but by calling a resize on the layout or something like that? I ask because I want to animate several widgets across my application and I want to find the best way to do this without having to make too many widgets interdependent upon each other.

Example code:

import sys
from PyQt4 import QtGui, QtCore


class AnimatedWidgets(QtGui.QWidget):
    def __init__(self):
        super(AnimatedWidgets, self).__init__()

        layout1 = QtGui.QVBoxLayout()
        self.setLayout(layout1)

        expanding_label = QtGui.QLabel("Expanding label!")
        expanding_label.setStyleSheet("border: 1px solid red")
        layout1.addWidget(expanding_label)

        self.file_model = QtGui.QFileSystemModel(self)
        sefl.file_model.setRootPath("C:/")
        self.browse_tree = QtGui.QTreeView()
        self.browse_tree.setModel(self.file_model)
        layout1.addWidget(self.browse_tree)

        shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
        shrink_tree_btn.clicked.connect(self.shrink_tree)
        layout1.addWidget(shrink_tree_btn)

        #--

        self.tree_size_anim = QtCore.QPropertyAnimation(self.browse_tree, "size")
        self.tree_size_anim.setDuration(1000)
        self.tree_size_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
        self.tree_pos_anim = QtCore.QPropertyAnimation(self.browse_tree, "pos")
        self.tree_pos_anim.setDuration(1000)
        self.tree_pos_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
        self.tree_anim_out = QtCore.QParallelAnimationGroup()
        self.tree_anim_out.addAnimation(self.tree_size_anim)
        self.tree_anim_out.addAnimation(self.tree_pos_anim)

    def shrink_tree(self):
        self.tree_size_anim.setStartValue(self.browse_tree.size())
        self.tree_size_anim.setEndValue(QtCore.QSize(self.browse_tree.width(), 0))

        tree_rect = self.browse_tree.geometry()
        self.tree_pos_anim.setStartValue(tree_rect.topLeft())
        self.tree_pos_anim.setEndValue(QtCore.QPoint(tree_rect.left(), tree_rect.bottom()))

        self.tree_anim_out.start()
        self.tree_anim_out.finished.connect(self.browse_tree.hide)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = AnimatedWidgets()

    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Solution

  • The layouts handle the geometry() of the widgets so that when wanting to change the pos property these are interfacing with their handles so it is very common that you get that type of behavior, a better option is to use a QVariantAnimation to establish a fixed height:

    import sys
    from PyQt4 import QtGui, QtCore
    
    
    class AnimatedWidgets(QtGui.QWidget):
        def __init__(self):
            super(AnimatedWidgets, self).__init__()
    
            layout1 = QtGui.QVBoxLayout(self)
    
            expanding_label = QtGui.QLabel("Expanding label!")
            expanding_label.setStyleSheet("border: 1px solid red")
            layout1.addWidget(expanding_label)
    
            self.file_model = QtGui.QFileSystemModel(self)
            self.file_model.setRootPath(QtCore.QDir.rootPath())
            self.browse_tree = QtGui.QTreeView()
            self.browse_tree.setModel(self.file_model)
            layout1.addWidget(self.browse_tree)
    
            shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
            shrink_tree_btn.clicked.connect(self.shrink_tree)
            layout1.addWidget(shrink_tree_btn)
            #--
            self.tree_anim = QtCore.QVariantAnimation(self)
            self.tree_anim.setDuration(1000)
            self.tree_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
    
        def shrink_tree(self):
            self.tree_anim.setStartValue(self.browse_tree.height())
            self.tree_anim.setEndValue(0)
            self.tree_anim.valueChanged.connect(self.on_valueChanged)
            self.tree_anim.start()
    
        def on_valueChanged(self, val):
            h, isValid = val.toInt()
            if isValid:
                self.browse_tree.setFixedHeight(h)
    
    def main():
        app = QtGui.QApplication(sys.argv)
        ex = AnimatedWidgets()
    
        ex.show()
        sys.exit(app.exec_())
    
    if __name__ == "__main__":
        main()