You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用QMovie实现单个Widget溶解效果且不影响底层控件?

单个Widget独立溶解效果实现问题

问题场景

已通过QMovie实现控件溶解效果,但无法做到仅溶解单个Widget而不影响后方控件:

  • 对整个窗口应用溶解效果时,表现正常:
    窗口整体溶解效果
  • 对单个红色QLabel应用效果时,后方控件也被影响,代码如下:
self.test_label = QLabel()
self.test_label.setFixedSize(300, 300)
self.test_label.setStyleSheet("background: red;")
self.test_label.setParent(self.body)
self.test_label.show()

self.test_animation = QMovie(":/animations/triangle.gif")
self.test_animation.start()

self.blend_test = BlendEffect(self.test_animation)
self.blend_test.setBlendMode(QPainter.CompositionMode.CompositionMode_Xor)
self.test_label.setGraphicsEffect(self.blend_test)

对应的异常效果:
单个控件溶解异常效果

需求:仅让红色QLabel实现溶解效果,不影响后方控件,该效果将用于QStackedWidget的页面切换。

解决方案

问题根源在于QGraphicsEffect的绘制上下文会包含后方控件内容,因此需要直接在Widget内部独立处理绘制逻辑,避免使用全局的GraphicsEffect。

方法1:自定义Widget重写paintEvent

通过自定义继承QLabel的类,在paintEvent中单独处理自身内容与动画帧的混合,确保仅修改当前Widget的显示:

from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPaintEvent
from PyQt5.QtCore import Qt, QMovie

class DissolvableLabel(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 初始化动画
        self.movie = QMovie(":/animations/triangle.gif")
        self.movie.frameChanged.connect(self.update)
        self.movie.start()

    def paintEvent(self, event):
        painter = QPainter(self)
        # 1. 缓存自身原本的内容到临时Pixmap
        temp_pixmap = QPixmap(self.size())
        temp_pixmap.fill(Qt.transparent)
        temp_painter = QPainter(temp_pixmap)
        # 调用父类的paintEvent绘制原本内容
        super().paintEvent(QPaintEvent(temp_pixmap.rect()))
        temp_painter.end()

        # 2. 获取当前动画帧并调整尺寸
        current_frame = self.movie.currentPixmap().scaled(
            self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
        )

        # 3. 使用混合模式实现溶解效果
        # 先绘制动画帧作为遮罩,用DestinationIn模式保留与帧重叠的区域
        painter.setCompositionMode(QPainter.CompositionMode_DestinationIn)
        painter.drawPixmap(self.rect(), current_frame)
        # 再绘制原本的内容
        painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
        painter.drawPixmap(self.rect(), temp_pixmap)

使用方式

直接实例化自定义类即可,无需设置GraphicsEffect:

self.test_label = DissolvableLabel()
self.test_label.setFixedSize(300, 300)
self.test_label.setStyleSheet("background: red;")
self.test_label.setParent(self.body)
self.test_label.show()

方法2:QStackedWidget页面切换适配

如果用于页面切换,可以将两个页面的内容缓存为Pixmap,通过动画帧控制两个Pixmap的混合,直接在自定义StackedWidget中绘制:

from PyQt5.QtWidgets import QStackedWidget
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt, QMovie

class DissolvableStackedWidget(QStackedWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.movie = QMovie(":/animations/triangle.gif")
        self.movie.frameChanged.connect(self.update)
        self.movie.start()
        self._current_page = 0

    def paintEvent(self, event):
        painter = QPainter(self)
        # 获取当前页和下一页的Pixmap
        current_pix = self.widget(self._current_page).grab()
        next_pix = self.widget((self._current_page + 1) % self.count()).grab()

        # 获取动画帧作为混合遮罩
        mask = self.movie.currentPixmap().scaled(self.size(), Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)

        # 绘制当前页
        painter.drawPixmap(self.rect(), current_pix)
        # 用遮罩控制下一页的显示区域
        painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
        painter.drawPixmap(self.rect(), mask)
        # 绘制下一页
        painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
        painter.drawPixmap(self.rect(), next_pix)

    def switch_page(self):
        self._current_page = (self._current_page + 1) % self.count()
        self.update()

内容的提问来源于stack exchange,提问作者Edward

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:37:01