如何解决为自定义标签几何设置动画时闪烁
我有一个自定义标签,它动态设置其内容边距以在重新缩放时保持其像素图的纵横比。 我需要这个,因为我想将此标签嵌入到自定义小部件中,该小部件在鼠标悬停时放大并在鼠标离开时缩小(有关详细信息,请参阅 this question)。
动画可以工作并且标签会调整大小,但它非常不稳定并且有很多闪烁:
出于某种原因,我似乎无法真正捕捉到这种闪烁的全部内容。它在我的屏幕上比在这个 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
我现在没有在调整大小时调整边距,而是直接绘制像素图,在此过程中将其居中,因此我什至不需要标签。
当矩形移出屏幕时,它们会缩小,我似乎无法摆脱它。 他们为什么这样做,我该如何阻止?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。