动画框架弹出应用程序的底部而不缩小其他元素的高度

Animating a frame to pop out of the bottom of the app without shrinking the height of other elements

我正在使用 PyQt 和 Qt creator 为 windows 开发桌面应用程序。

我想要的

我只想在用户输入时向用户显示消息。我还希望消息能吸引眼球,所以我将采用以下动画解决方案:

不需要时隐藏的框架(高度 = 0,宽度 = 应用程序的宽度),'grows' 在需要时从应用程序底部隐藏,保持可见 5-6 秒,然后缩回到底部。

没有消息的应用看起来像这样:

显示消息时有点像这样(请注意消息底部的灰色元素 'covered'):

我试过的

所以我这样做的方法是创建我称之为“页脚框架”的东西,它包含另一个我称之为“消息框架”的框架。消息框架包含一个标签,该标签将及时为用户保存消息。所有东西都有预先确定的高度,所以为了隐藏整个东西,我将消息框架的最大高度设置为 0。

因此,对于 'growing' 动画,我对消息框的最大高度 属性 进行了动画处理。

当前问题

事情是这样的 - 因为我希望应用程序能够响应,所以我将所有内容都放在布局中......因此,无论何时显示消息,其余组件的高度都是 'compressed'。 有点像这样(注意底部的灰色元素是如何没有被消息覆盖的,但是所有元素的高度都缩小了一点):

相反,我希望消息发送到 'cover' 消息坐标下的任何位置。

我试图为消息框的几何图形设置动画,但实际上什么也没有发生——可能是因为最小高度仍然是 0。所以我试图在动画开始之前更改最小高度;但这又导致了压缩。 尝试对页脚框架执行相同的操作,结果相同。

我的问题是:使用 Qt 实现我想要的结果的最佳/首选方法是什么?

布局管理员总是尝试显示他们正在管理的所有 小部件。如果你想让一个小部件与其他小部件重叠,你不能将它放在布局中,你只需创建具有父级的小部件,并且该父级可能是包含上面 布局的小部件顶级 window.

这不能在 Designer/Creator 中完成,因为假定一旦为父窗口小部件设置了布局,所有子窗口小部件都将由该布局管理。唯一的解决方案是以编程方式执行此操作。

在下面的示例中,我假设使用了 QMainWindow,因此参考父窗口小部件实际上是 中央窗口小部件,而不是 QMainWindow:那是因为警报应该 覆盖作为主要 window 布局一部分的其他小部件,例如状态栏或底部放置的工具栏或停靠栏。

动画其实是一个QSequentialAnimationGroup that shows the rectangle, waits a few seconds, and hides it again. Since the window could be resized while the animation is running, a helper function is used to properly update the start and end values of the warning and eventually update the geometry when in the "paused" state (which is actually a QPauseAnimation);为此,在中央小部件上安装了一个事件过滤器。

from random import randrange
from PyQt5 import QtCore, QtWidgets, uic

class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('overlay.ui', self)

        self.alerts = []

        self.centralWidget().installEventFilter(self)

        self.pushButton.clicked.connect(self.showAlert)
        QtCore.QTimer.singleShot(2000, self.showAlert)

    def showAlert(self, message=None, timeout=250):
        # create an alert that is a child of the central widget
        alert = QtWidgets.QLabel(message or 'Some message to the user', 
            self.centralWidget(), wordWrap=True, 
            alignment=QtCore.Qt.AlignCenter, 
            styleSheet='background: rgb({}, {}, {});'.format(
                randrange(192, 255), randrange(192, 255), randrange(192, 255)))
        self.alerts.append(alert)
        alert.animation = QtCore.QSequentialAnimationGroup(alert)
        alert.animation.addAnimation(QtCore.QPropertyAnimation(
            alert, b'geometry', duration=timeout))
        alert.animation.addAnimation(QtCore.QPauseAnimation(3000))
        alert.animation.addAnimation(QtCore.QPropertyAnimation(
            alert, b'geometry', duration=timeout))

        # delete the alert when the animation finishes
        def deleteLater():
            self.alerts.remove(alert)
            alert.deleteLater()
        alert.animation.finished.connect(deleteLater)

        # update all animations, including the new one; this is not very
        # performant, as it also updates all existing alerts; it is 
        # just done for simplicity;
        self.updateAnimations()
        # set the start geometry of the alert, show it, and start 
        # the new animation
        alert.setGeometry(alert.animation.animationAt(0).startValue())
        alert.show()
        alert.animation.start()

    def updateAnimations(self):
        width = self.centralWidget().width() - 20
        y = self.centralWidget().height()
        margin = self.fontMetrics().height() * 2
        for alert in self.alerts:
            height = alert.heightForWidth(width) + margin
            startRect = QtCore.QRect(10, y, width, height)
            endRect = startRect.translated(0, -height)
            alert.animation.animationAt(0).setStartValue(startRect)
            alert.animation.animationAt(0).setEndValue(endRect)
            alert.animation.animationAt(2).setStartValue(endRect)
            alert.animation.animationAt(2).setEndValue(startRect)

    def eventFilter(self, obj, event):
        if obj == self.centralWidget() and event.type() == event.Resize and self.alerts:
            self.updateAnimations()
            for alert in self.alerts:
                ani = alert.animation
                # if the animation is "paused", update the geometry
                if isinstance(ani.currentAnimation(), QtCore.QPauseAnimation):
                    alert.setGeometry(ani.animationAt(0).endValue())
        return super().eventFilter(obj, event)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec())