当前位置: 首页 > article >正文

pyqt瀑布流布局

最近研究瀑布流布局,发现都是收费的,所以只能自己写算法写布局。
所以啥都不说直接上代码

ImageLabel

参考 pyqt5 QLabel显示网络图片或qfluentwidgets官网

代码

import math
import sys
from pathlib import Path

from PyQt5.Qt import *
from qfluentwidgets import ImageLabel


class FlowLayout(QLayout):
    """ 流布局 """

    def __init__(self, parent=None, needAni=False, isTight=False):
        """
        Parameters
        ----------
        parent:
            parent window or layout

        needAni: bool
            whether to add moving animation

        isTight: bool
            whether to use the tight layout when widgets are hidden
        """
        super().__init__(parent)
        self._items = []  # type: List[QLayoutItem]
        self._anis = []  # type: List[QPropertyAnimation]
        self._aniGroup = QParallelAnimationGroup(self)
        self._verticalSpacing = 10
        self._horizontalSpacing = 10
        self.duration = 300
        self.ease = QEasingCurve.Linear
        self.needAni = needAni
        self.isTight = isTight
        self._deBounceTimer = QTimer(self)
        self._deBounceTimer.setSingleShot(True)
        self._deBounceTimer.timeout.connect(lambda: self._doLayout(self.geometry(), True))
        self._wParent = None
        self._isInstalledEventFilter = False

    def addItem(self, item):
        self._items.append(item)

    def insertItem(self, index, item):
        self._items.insert(index, item)

    def addWidget(self, w):
        super().addWidget(w)
        self._onWidgetAdded(w)

    def insertWidget(self, index, w):
        self.insertItem(index, QWidgetItem(w))
        self.addChildWidget(w)
        self._onWidgetAdded(w, index)

    def _onWidgetAdded(self, w, index=-1):
        if not self._isInstalledEventFilter:
            if w.parent():
                self._wParent = w.parent()
                w.parent().installEventFilter(self)
            else:
                w.installEventFilter(self)

        if not self.needAni:
            return

        ani = QPropertyAnimation(w, b'geometry')
        ani.setEndValue(QRect(QPoint(0, 0), w.size()))
        ani.setDuration(self.duration)
        ani.setEasingCurve(self.ease)
        w.setProperty('flowAni', ani)
        self._aniGroup.addAnimation(ani)

        if index == -1:
            self._anis.append(ani)
        else:
            self._anis.insert(index, ani)

    def setAnimation(self, duration, ease=QEasingCurve.Linear):
        """ set the moving animation

        Parameters
        ----------
        duration: int
            the duration of animation in milliseconds

        ease: QEasingCurve
            the easing curve of animation
        """
        if not self.needAni:
            return

        self.duration = duration
        self.ease = ease

        for ani in self._anis:
            ani.setDuration(duration)
            ani.setEasingCurve(ease)

    def count(self):
        return len(self._items)

    def itemAt(self, index: int):
        if 0 <= index < len(self._items):
            return self._items[index]

        return None

    def takeAt(self, index: int):
        if 0 <= index < len(self._items):
            item = self._items[index]  # type: QLayoutItem
            ani = item.widget().property('flowAni')
            if ani:
                self._anis.remove(ani)
                self._aniGroup.removeAnimation(ani)
                ani.deleteLater()

            return self._items.pop(index).widget()

        return None

    def removeWidget(self, widget):
        for i, item in enumerate(self._items):
            if item.widget() is widget:
                return self.takeAt(i)

    def removeAllWidgets(self):
        """ remove all widgets from layout """
        while self._items:
            self.takeAt(0)

    def takeAllWidgets(self):
        """ remove all widgets from layout and delete them """
        while self._items:
            w = self.takeAt(0)
            if w:
                w.deleteLater()

    def expandingDirections(self):
        return Qt.Orientation(0)

    def hasHeightForWidth(self):
        return True

    def heightForWidth(self, width: int):
        """ get the minimal height according to width """
        return self._doLayout(QRect(0, 0, width, 0), False)

    def setGeometry(self, rect: QRect):
        super().setGeometry(rect)

        if self.needAni:
            self._deBounceTimer.start(80)
        else:
            self._doLayout(rect, True)

    def sizeHint(self):
        return self.minimumSize()

    def minimumSize(self):
        size = QSize()

        for item in self._items:
            size = size.expandedTo(item.minimumSize())

        m = self.contentsMargins()
        size += QSize(m.left() + m.right(), m.top() + m.bottom())

        return size

    def setVerticalSpacing(self, spacing: int):
        """ set vertical spacing between widgets """
        self._verticalSpacing = spacing

    def verticalSpacing(self):
        """ get vertical spacing between widgets """
        return self._verticalSpacing

    def setHorizontalSpacing(self, spacing: int):
        """ set horizontal spacing between widgets """
        self._horizontalSpacing = spacing

    def horizontalSpacing(self):
        """ get horizontal spacing between widgets """
        return self._horizontalSpacing

    def eventFilter(self, obj: QObject, event: QEvent) -> bool:
        if obj in [w.widget() for w in self._items] and event.type() == QEvent.Type.ParentChange:
            self._wParent = obj.parent()
            obj.parent().installEventFilter(self)
            self._isInstalledEventFilter = True

        if obj == self._wParent and event.type() == QEvent.Type.Show:
            self._doLayout(self.geometry(), True)
            self._isInstalledEventFilter = True

        return super().eventFilter(obj, event)

    def _doLayout(self, rect: QRect, move: bool):
        """ adjust widgets position according to the window size """
        aniRestart = False
        margin = self.contentsMargins()
        x = rect.x() + margin.left()
        y = rect.y() + margin.top()
        rowHeight = 0
        spaceX = self.horizontalSpacing()
        spaceY = self.verticalSpacing()

        for i, item in enumerate(self._items):
            if item.widget() and not item.widget().isVisible() and self.isTight:
                continue

            nextX = x + item.sizeHint().width() + spaceX

            if nextX - spaceX > rect.right() - margin.right() and rowHeight > 0:
                x = rect.x() + margin.left()
                y = y + rowHeight + spaceY
                nextX = x + item.sizeHint().width() + spaceX
                rowHeight = 0

            if move:
                target = QRect(QPoint(x, y), item.sizeHint())
                if not self.needAni:
                    item.setGeometry(target)
                elif target != self._anis[i].endValue():
                    self._anis[i].stop()
                    self._anis[i].setEndValue(target)
                    aniRestart = True

            x = nextX
            rowHeight = max(rowHeight, item.sizeHint().height())

        if self.needAni and aniRestart:
            self._aniGroup.stop()
            self._aniGroup.start()

        return y + rowHeight + margin.bottom() - rect.y()


class WaterFallFlowLayout(FlowLayout):
    """ 瀑布流布局 """

    def _doLayout(self, rect: QRect, move: bool):
        """ 重写布局函数 """
        aniRestart = False
        # 获取item的宽度
        margin = self.contentsMargins()
        item_width = self._items[0].sizeHint().width()
        # 计算列数
        columnCount = math.floor(rect.width() / item_width)
        # 获取spacing
        spaceX = self.horizontalSpacing()
        spaceY = self.verticalSpacing()
        # 布局,根据布局调整左边宽度,默认是居中的,若靠左left=0,靠右2 * left
        left = (rect.width() - item_width * columnCount - spaceX * (columnCount - 1)) // 2
        hrr = []
        for index, item in enumerate(self._items):
            item_size = item.sizeHint()
            if index < columnCount:
                target = QRect(QPoint(left + index * (item_width + spaceX), spaceY), item_size)
                hrr.append(item_size.height() + spaceY)
            else:
                minHeight = min(hrr)
                i = hrr.index(minHeight)
                target = QRect(QPoint(left + i * (item_width + spaceX), minHeight + spaceY), item_size)
                hrr[i] += item_size.height() + spaceY

            if not self.needAni:
                item.setGeometry(target)
            elif target != self._anis[index].endValue():
                self._anis[index].stop()
                self._anis[index].setEndValue(target)
                aniRestart = True

        if self.needAni and aniRestart:
            self._aniGroup.stop()
            self._aniGroup.start()

        return max(hrr)


class Window(QScrollArea):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.widget = QWidget()
        self.waterfallLayout = WaterFallFlowLayout(self.widget)
        pngs = Path(r"G:\手机\壁纸\手机壁纸").glob("**/*.png")
        for i, file in enumerate(pngs):
            pixmap = QPixmap(str(file))
            if not pixmap.isNull():
                imageLabel = ImageLabel()
                imageLabel.setBorderRadius(8, 8, 8, 8)
                imageLabel.setPixmap(pixmap)
                imageLabel.scaledToWidth(300)
                self.waterfallLayout.addWidget(imageLabel)
            if i == 30:
                break
        self.__initWidgets()

    def __initWidgets(self):
        self.widget.setAutoFillBackground(False)
        self.setWidget(self.widget)
        self.setWidgetResizable(True)


if __name__ == '__main__':
    QApplication.setHighDpiScaleFactorRoundingPolicy(
        Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)

    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(True)
    demo = Window()
    demo.resize(800, 600)
    demo.show()
    sys.exit(app.exec_())

预览图

在这里插入图片描述


http://www.kler.cn/a/322298.html

相关文章:

  • qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能
  • 面试题: Spring中的事务是如何实现的?
  • 【MYSQL】锁详解(全局锁、表级锁、行级锁)【快速理解】
  • 4A架构之间的关系和集成
  • A037-基于Spring Boot的二手物品交易的设计与实现
  • 深度学习:计算卷积神经网络中输出特征图尺寸的关键公式
  • java提升-常见的java调试工具介绍
  • 在 Kubernetes 上部署 .NET 应用的完整指南:从容器化到自动化 CI/CD
  • 【深度学习】03-神经网络01-4 神经网络的pytorch搭建和参数计算
  • BiGRU——提高基于 RNN免疫反应预测的准确性
  • 矩阵学习过程中的一些思考
  • 关于LlamaIndex 的几种索引方式介绍
  • es高级查询——多字段聚合
  • Spring在不同类型之间也能相互拷贝?
  • 【java17】java17新特性之Stream.toList()
  • 3271.哈希分割子串
  • 前端使用vue点击上传文件,传送给后端,后端进行文件接收
  • 区块链—共享块存储安全可信任可追踪分布式数据库系统技术
  • LTE SSS产生过程中z序列的介绍和MATLAB以及C语言实现
  • 7个不为人知的实用软件推荐
  • 关于VSCode里需要用的一些插件总结
  • 19 基于51单片机的倒计时音乐播放系统设计
  • USB 3.1 标准 A 型到 USB 3.1 标准 A 型或 B 型的电缆组件
  • Spring MVC 基础 : 文件、cookies的接收 ,REST响应
  • python命令行怎么换行
  • Python批量处理客户明细表格数据,挖掘更大价值