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

Qt小组件 - 2(布局)瀑布流布局,GridLayout,FlowLayout

分享两个布局

FlowLayout

流式布局,从左到右排序

# coding:utf-8
from typing import Listfrom PySide6.QtCore import QSize, QPoint, Qt, QRect, QPropertyAnimation, QParallelAnimationGroup, QEasingCurve, QEvent, QTimer, QObject
from PySide6.QtWidgets import QLayout, QWidgetItem, QLayoutItemclass FlowLayout(QLayout):""" Flow layout """def __init__(self, parent=None, needAni=False, isTight=False):"""Parameters----------parent:parent window or layoutneedAni: boolwhether to add moving animationisTight: boolwhether 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 = 10self._horizontalSpacing = 10self.duration = 300self.ease = QEasingCurve.Linearself.needAni = needAniself.isTight = isTightself._deBounceTimer = QTimer(self)self._deBounceTimer.setSingleShot(True)self._deBounceTimer.timeout.connect(lambda: self._doLayout(self.geometry(), True))self._wParent = Noneself._isInstalledEventFilter = Falsedef 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:returnani = 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 animationParameters----------duration: intthe duration of animation in millisecondsease: QEasingCurvethe easing curve of animation"""if not self.needAni:returnself.duration = durationself.ease = easefor 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 Nonedef takeAt(self, index: int):if 0 <= index < len(self._items):item = self._items[index]   # type: QLayoutItemani = item.widget().property('flowAni')if ani:self._anis.remove(ani)self._aniGroup.removeAnimation(ani)ani.deleteLater()return self._items.pop(index).widget()return Nonedef 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 Truedef 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 sizedef setVerticalSpacing(self, spacing: int):""" set vertical spacing between widgets """self._verticalSpacing = spacingdef verticalSpacing(self):""" get vertical spacing between widgets """return self._verticalSpacingdef setHorizontalSpacing(self, spacing: int):""" set horizontal spacing between widgets """self._horizontalSpacing = spacingdef horizontalSpacing(self):""" get horizontal spacing between widgets """return self._horizontalSpacingdef 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 = Trueif obj == self._wParent and event.type() == QEvent.Type.Show:self._doLayout(self.geometry(), True)self._isInstalledEventFilter = Truereturn super().eventFilter(obj, event)def _doLayout(self, rect: QRect, move: bool):""" adjust widgets position according to the window size """aniRestart = Falsemargin = self.contentsMargins()x = rect.x() + margin.left()y = rect.y() + margin.top()rowHeight = 0spaceX = self.horizontalSpacing()spaceY = self.verticalSpacing()for i, item in enumerate(self._items):if item.widget() and not item.widget().isVisible() and self.isTight:continuenextX = x + item.sizeHint().width() + spaceXif nextX - spaceX > rect.right() - margin.right() and rowHeight > 0:x = rect.x() + margin.left()y = y + rowHeight + spaceYnextX = x + item.sizeHint().width() + spaceXrowHeight = 0if 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 = Truex = nextXrowHeight = max(rowHeight, item.sizeHint().height())if self.needAni and aniRestart:self._aniGroup.stop()self._aniGroup.start()return y + rowHeight + margin.bottom() - rect.y()

例子

from random import randintfrom PySide6.QtWidgets import QWidget, QPushButton, QApplication
from flowLayout import FlowLayoutclass MyWidget(QWidget):def __init__(self):super().__init__()self.flowLayout = FlowLayout(self)for i in range(20):btn = QPushButton(f"Button {i + 1}")btn.setMinimumWidth(randint(100, 300))self.flowLayout.addWidget(btn)if __name__ == '__main__':app = QApplication()w = MyWidget()w.show()app.exec()

在这里插入图片描述

WaterfallLayout

瀑布流布局,布局内部使用scaledToWidth函数来设置高度,如果不设置scaledToWidth函数,默认保持原有的控件比例进行拉伸,弥补QGridLayout无法拉伸控件高度的缺点,以及无法自适应列数,一个仿VUEGridLayout

# coding: utf-8
from pathlib import Pathfrom PySide6.QtCore import QRect, QPoint, QSize, Property
from PySide6.QtWidgets import QWidget, QScrollArea
from flowLayout import FlowLayoutclass WaterfallLayout(FlowLayout):def __init__(self, parent=None):super().__init__(parent, False, False)self._itemMinWidth = 200self._geometry = self.geometry()def setItemMinimumWidth(self, width: int):self._itemMinWidth = widthself._doLayout(self.geometry(), True)def getItemMinimumWidth(self):return self._itemMinWidthdef _doLayout(self, rect: QRect, move: bool):aniRestart = Falsemargin = self.contentsMargins()left = rect.x() + margin.left()top = rect.y() + margin.top()spaceX = self.horizontalSpacing()spaceY = self.verticalSpacing()availableWidth = rect.width() - left - margin.right()columns = max(1, (availableWidth + spaceX) // (self.itemMinimumWidth + spaceX))itemWidth = int((availableWidth - (columns - 1) * spaceX) / columns)columnHeights = [top] * columnsfor i, item in enumerate(self._items):if item.widget() and not item.widget().isVisible() and self.isTight:continuewidget = item.widget()if hasattr(widget, 'scaledToWidth'):widget.scaledToWidth(itemWidth)height = widget.height()else:than = widget.height() / widget.width()  # 宽高比height = int(itemWidth * than)column = min(columnHeights.index(min(columnHeights)), columns - 1)x = left + column * (itemWidth + spaceX)y = columnHeights[column]if move:target = QRect(QPoint(x, y), QSize(itemWidth, height))if not self.needAni:item.setGeometry(target)elif target != self._anis[i].endValue():self._anis[i].stop()self._anis[i].setEndValue(target)aniRestart = TruecolumnHeights[column] += height + spaceYif self.needAni and aniRestart:self._aniGroup.stop()self._aniGroup.start()return top + max(columnHeights) + margin.bottom() - rect.y()itemMinimumWidth = Property(int, getItemMinimumWidth, setItemMinimumWidth)

例子

ImageLabel可参考https://blog.csdn.net/weixin_54217201/article/details/149336017?spm=1011.2415.3001.5331

scaledToWidth的组件

# coding: utf-8
from pathlib import Pathfrom PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget, QScrollAreafrom components import ImageLabel
from waterfallLayout import WaterfallLayoutclass MyWidget(QScrollArea):def __init__(self, parent=None):super().__init__(parent)self.setWidget(QWidget())self.setWidgetResizable(True)self.flowLayout = WaterfallLayout(self.widget())self.flowLayout.setItemMinimumWidth(350)self.widget().setLayout(self.flowLayout)for file in list(Path(r'G:\手机\壁纸').glob('*.*'))[:5]:item = ImageLabel()item.setRadius(5)item.setIsCenter(True)item.setImage(file)item.setMinimumSize(QSize(300, 200))self.flowLayout.addWidget(item)if __name__ == '__main__':import sysfrom PySide6.QtWidgets import QApplicationapp = QApplication(sys.argv)w = MyWidget()w.resize(867, 628)w.show()sys.exit(app.exec())

在这里插入图片描述

没有 scaledToWidth组件

setItemMinimumWidth的值必须大于item.width(),需要设置setMinimumSize否则无法换行,找半天,但是没找到原因

from pathlib import Pathfrom PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget, QScrollAreafrom components import ImageLabel
from waterfallLayout import WaterfallLayoutclass MyWidget(QScrollArea):def __init__(self, parent=None):super().__init__(parent)self.setWidget(QWidget())self.setWidgetResizable(True)self.flowLayout = WaterfallLayout(self.widget())self.flowLayout.setItemMinimumWidth(350)self.widget().setLayout(self.flowLayout)for file in list(Path(r'G:\手机\壁纸').glob('*.*'))[:10]:item = QLabel()item.setPixmap(QPixmap(file))item.setMinimumSize(QSize(300, 200))item.setScaledContents(True)self.flowLayout.addWidget(item)if __name__ == '__main__':import sysfrom PySide6.QtWidgets import QApplicationapp = QApplication(sys.argv)w = MyWidget()w.resize(867, 628)w.show()sys.exit(app.exec())

没有设置scaledToWidth

http://www.lryc.cn/news/588274.html

相关文章:

  • 虚拟列表组件如果滑动速度过快导致渲染性能问题
  • UART寄存器介绍
  • 前端学习5:Float学习(仅简单了解,引出flex)
  • 015 程序地址空间入门
  • Life:Internship in OnSea Day 22
  • 某ctv视频下载逆向思路
  • 云原生技术与应用-Containerd容器技术详解
  • Git LFS 操作处理Github上传大文件操作记录
  • Spring Boot 双数据源配置
  • RPC vs RESTful架构选择背后的技术博弈
  • SecretFlow 隐语 (2) --- 隐语架构概览
  • 【安卓笔记】进程和线程的基础知识
  • Ubuntu20.05上安装Clang 15
  • 【安卓笔记】线程基本使用:锁、锁案例
  • 新型eSIM攻击技术可克隆用户资料并劫持手机身份
  • linux 内核: 访问当前进程的 task_struct
  • [论文阅读] 人工智能 + 软件工程 | 用大语言模型+排名机制,让代码评论自动更新更靠谱
  • android Perfetto cpu分析教程及案例
  • 迁移学习之图像预训练理解
  • ICML 2025 | 从语言到视觉,自回归模型VARSR开启图像超分新范式
  • C# TCP粘包与拆包深度了解
  • CSP-S 模拟赛 17
  • 哈希扩展 --- 海量数据处理
  • 一文明白AI、AIGC、LLM、GPT、Agent、workFlow、MCP、RAG概念与关系
  • Linux操作系统从入门到实战(七)详细讲解编辑器Vim
  • 螺旋模型:风险分析驱动的渐进式开发
  • C++卸载了会影响电脑正常使用吗?解析C++运行库的作用与卸载后果
  • 什么是实时数仓?实时数仓又有哪些应用场景?
  • 疯狂星期四 - 第7天运营日报
  • 多线程/协程环境时间获取的“时间片陷阱“:深度解析与工程级解决方案