为自定义标签几何设置动画时闪烁

问题描述

我有一个自定义标签,它动态设置其内容边距以在重新缩放时保持其像素图的纵横比。 我需要这个,因为我想将此标签嵌入到自定义小部件中,该小部件在鼠标悬停时放大并在鼠标离开时缩小(有关详细信息,请参阅 this question)。

动画可以工作并且标签会调整大小,但它非常不稳定并且有很多闪烁:

Two blue rectangles. On mouse-over the first is scaled up while flickering

出于某种原因,我似乎无法真正捕捉到这种闪烁的全部内容。它在我的屏幕上比在这个 GIF 中更明显。
它几乎看起来像一些时髦的EasingCurve,但它被设置为“线性”,所以它不应该那样表现。

这是我的代码

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


class CustomScrollArea(QScrollArea):

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)

        self._current_index = -1
        self.setVerticalScrollBarPolicy(Qt.ScrollBaralwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBaralwaysOff)

        # Layout & ScrollWidget
        scroll_widget = QWidget()
        scroll_widget.setStyleSheet('.QWidget{background: transparent}')
        self.setWidget(scroll_widget)
        self.setWidgetResizable(True)

        self._layout = QVBoxLayout()
        self._layout.setSpacing(50)
        self._layout.setContentsMargins(0,0)

        scroll_widget.setLayout(self._layout)

        # Add Stretch
        self._layout.addStretch(1)
        self._layout.addStretch(1)

        # Animation
        self._scroll_anim = QPropertyAnimation(self.verticalScrollBar(),b'value')
        self._scroll_anim.setDuration(250)
        self._scroll_anim.setEasingCurve(QEasingCurve.OutQuad)

        # Initialize
        for _ in range(3):
            self.add_item()

    def add_item(self):
        self._layout.insertWidget(
            len(self.items()) + 1,CustomScrollItem(),alignment=Qt.AlignCenter
        )

        if len(self.items()) == 1:
            self._current_index = 0

    def items(self):
        # Skip layout and return all other children
        return self.widget().children()[1:]

    def scroll_to(self,index):
        items = self.items()

        if index < 0 or index >= len(items) or index == self._current_index:
            return

        cur_item = items[self._current_index]
        next_item = items[index]

        pos_delta = next_item.pos().y() - cur_item.pos().y()
        sb = self.verticalScrollBar()

        self._scroll_anim.setEndValue(sb.value() + pos_delta)
        self._scroll_anim.start()

        # disconnect all lambdas from prevIoUs scrolls (raises Error if none)
        try:
            self._scroll_anim.finished.disconnect()
        except TypeError:
            pass
        # Set the new index after the animation has finished
        self._scroll_anim.finished.connect(
            lambda x=index: self._set_scroll_index(x)
        )

    def resizeEvent(self,event: QResizeEvent) -> None:
        super().resizeEvent(event)

        child_widgets = self.items()

        if len(child_widgets) < 2:
            return

        first = child_widgets[0]
        last = child_widgets[-1]

        self._layout.setContentsMargins(
            0,int(event.size().height() / 2 - first.size().height() / 2),int(event.size().height() / 2 - last.size().height() / 2)
        )

    def wheelEvent(self,event: QWheelEvent) -> None:
        if self._scroll_anim.state() == QAbstractAnimation.Running:
            return

        if event.angleDelta().y() < 0:
            self.scroll_to(self._current_index + 1)
        else:
            self.scroll_to(self._current_index - 1)

    def keyPressEvent(self,event: QKeyEvent) -> None:
        if self._scroll_anim.state() == QAbstractAnimation.Running:
            return

        if event.key() == Qt.Key_PageDown:
            self.scroll_to(len(self.items()) - 1)

        elif event.key() == Qt.Key_PageUp:
            self.scroll_to(0)

        elif event.key() == Qt.Key_Up:
            self.scroll_to(self._current_index - 1)

        elif event.key() == Qt.Key_Down:
            self.scroll_to(self._current_index + 1)

    def _set_scroll_index(self,index):
        """
        This function should **only** be called by the lambda-function
        connected to the scroll-animation
        """
        self._current_index = index


class CustomScrollItem(QWidget):

    def __init__(self,**kwargs)

        self.image_bounds = QSize(140,175)

        # Set up layout
        self.layout = QVBoxLayout()
        self.layout.setSpacing(0)
        self.setLayout(self.layout)

        # pixmap
        self.pixmap_lbl = AspectRatioLabel()

        pix = Qpixmap(self.image_bounds)
        pix.fill(QColor('blue'))
        self.pixmap_lbl.setpixmap(pix)

        self.layout.addWidget(self.pixmap_lbl,stretch=1)

        # Text
        self.title_lbl = QLabel('Text beneath')
        self.title_lbl.setSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)
        self.title_lbl.setStyleSheet('color: #000000')
        font = self.title_lbl.font()
        font.setPointSize(13)
        self.title_lbl.setFont(font)
        self.layout.addWidget(self.title_lbl,alignment=Qt.AlignHCenter)

        # Mouse-Over animation
        self.zoom_factor = 1.15

        self.anim = QPropertyAnimation(self,b'geometry')
        self.anim.setEasingCurve(QEasingCurve.Linear)
        self.anim.setDuration(150)

    def enterEvent(self,event: QEvent) -> None:
        super().enterEvent(event)

        # Mouse-Over animation
        initial_rect = self.geometry()
        final_rect = QRect(
            initial_rect.left(),initial_rect.top(),int(initial_rect.width() * self.zoom_factor),int(initial_rect.height() * self.zoom_factor),)
        final_rect.moveCenter(initial_rect.center())

        self.anim.setStartValue(initial_rect)
        self.anim.setEndValue(final_rect)
        self.anim.setDirection(QAbstractAnimation.Forward)
        self.anim.start()

    def leaveEvent(self,event: QEvent) -> None:
        super().leaveEvent(event)

        self.anim.setDirection(QAbstractAnimation.Backward)
        self.anim.start()


class AspectRatioLabel(QLabel):

    def __init__(self,parent=None,*args):
        super().__init__(parent,*args)

        self.pix = None
        self.setScaledContents(True)

    def setpixmap(self,pix: Qpixmap) -> None:
        super().setpixmap(pix)

        if not self.pix and isinstance(pix,Qpixmap):
            self.pix = pix

    def resizeEvent(self,event: QResizeEvent) -> None:
        """
        Set the contentsMargin so that the pixmap keeps its aspect ratio when
        being scaled.

        """
        if not self.pix:
            return

        ratio = self.pix.height() / self.pix.width()

        # Determine which side is too big
        size_1 = QSize(
            int(event.size().height() / ratio),event.size().height()
        )
        size_2 = QSize(
            event.size().width(),int(event.size().width() * ratio)
        )

        if size_1 == size_2:
            return

        # Size with adjusted width fits
        if (size_1.height() <= event.size().height()
                and size_1.width() <= event.size().width()):
            excess_width = int((event.size().width() - size_1.width()) / 2)
            self.setContentsMargins(excess_width,excess_width,0)

        # Size width adjusted height fits
        else:
            excess_height = int((event.size().height() - size_2.height()) / 2)
            self.setContentsMargins(0,excess_height,excess_height)

        super().resizeEvent(event)


if __name__ == '__main__':
    app = QApplication([])
    window = CustomScrollArea()
    window.show()
    app.exec()

这种效果可能是由于标签边距不断更新造成的,但我想不出更好的方法来做到这一点。


编辑

好的,我通过用以下代码替换 AspectRatioLabel 来实现平滑缩放:

class AspectRatioLabel(QWidget):

    def __init__(self,*args)

        self.pix = None

    def paintEvent(self,event: QPaintEvent) -> None:
        super().paintEvent(event)

        if not self.pix:
            return

        painter = QPainter(self)

        pixSize = self.pix.size()
        pixSize.scale(event.rect().size(),Qt.KeepAspectRatio)

        scaled_pixmap = self.pix.scaled(
            pixSize,Qt.KeepAspectRatio,Qt.SmoothTransformation
        )

        position = QPoint(
            int((event.rect().size().width() - pixSize.width()) / 2),int((event.rect().height() - pixSize.height()) / 2)
        )
        painter.drawpixmap(position,scaled_pixmap)

    def pixmap(self) -> Qpixmap:
        return self.pix

    def setpixmap(self,pixmap: Qpixmap) -> None:
        self.pix = pixmap

我现在没有在调整大小时调整边距,而是直接绘制像素图,在此过程中将其居中,因此我什至不需要标签

但现在我有一个不同的问题:

GIF showing three blue rectangles being resized smoothly,but when scrolling,the outer ones shrink

当矩形移出屏幕时,它们会缩小,我似乎无法摆脱它。 他们为什么这样做,我该如何阻止?

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)