Qt小组件 - 1(手风琴)
代码
card.py
# coding: utf-8
import sys
from pathlib import Path
from typing import Unionfrom PySide6.QtCore import QPropertyAnimation, QEasingCurve, Property, Qt, Signal, QEvent
from PySide6.QtGui import QPainter, QPixmap, QImage, QPaintEvent, QPainterPath, QEnterEvent, \QMouseEvent
from PySide6.QtWidgets import QWidget, QApplication, QMainWindowclass AccordionCard(QWidget):hovered = Signal(object, bool)stretchChanged = Signal(object, float)clicked = Signal()def __init__(self, image: Union[str, Path, QPixmap, QImage], parent=None):super().__init__(parent)self.image = QImage()self.__stretch = 1.0self.__radius = 5.0self.animation = QPropertyAnimation(self, b'stretch', self)self.animation.setDuration(200)self.animation.setEasingCurve(QEasingCurve.Type.OutQuart)self.animation.valueChanged.connect(lambda value: self.stretchChanged.emit(self, value))self.setImage(image)def setImage(self, image: Union[str, Path, QPixmap, QImage]):if isinstance(image, str):image = QImage(image)elif isinstance(image, Path):image = QImage(image.as_posix())elif isinstance(image, QPixmap):image = image.toImage()self.image = imagedef getStretch(self) -> float:return self.__stretchdef setStretch(self, stretch: float):if self.__stretch == stretch:returnself.__stretch = stretchself.updateGeometry()def setRadius(self, radius: float):self.__radius = radiusself.update()def getRadius(self) -> float:return self.__radiusdef setTargetStretch(self, stretch: float):if self.animation.state() == QPropertyAnimation.State.Running:self.animation.stop()self.animation.setStartValue(self.stretch)self.animation.setEndValue(stretch)self.animation.start()def enterEvent(self, event: QEnterEvent):self.hovered.emit(self, True)super().enterEvent(event)def leaveEvent(self, event: QEvent):self.hovered.emit(self, False)super().leaveEvent(event)def mouseReleaseEvent(self, event: QMouseEvent):if event.button() == Qt.MouseButton.LeftButton:self.clicked.emit()return super().mouseReleaseEvent(event)def paintEvent(self, event: QPaintEvent):painter = QPainter(self)painter.setRenderHints(QPainter.RenderHint.Antialiasing |QPainter.RenderHint.SmoothPixmapTransform |QPainter.RenderHint.LosslessImageRendering)path = QPainterPath()path.addRoundedRect(self.rect(), self.radius, self.radius)painter.setClipPath(path)image = self.image.scaled(self.size() * self.devicePixelRatioF(),Qt.AspectRatioMode.KeepAspectRatioByExpanding,Qt.TransformationMode.SmoothTransformation) # type: QImageiw, ih = image.width(), image.height()w = self.width() * self.devicePixelRatioF()h = self.height() * self.devicePixelRatioF()x, y = (iw - w) / 2, (ih - h) / 2image = image.copy(int(x), int(y), int(w), int(h))painter.drawImage(self.rect(), image)stretch = Property(float, getStretch, setStretch)radius = Property(float, getRadius, setRadius)if __name__ == '__main__':app = QApplication(sys.argv)window = QMainWindow()window.resize(400, 400)window.setContentsMargins(20, 20, 20, 20)card = AccordionCard('img/FvCpS5naYAA06ej.jpg')card.hovered.connect(print)window.setCentralWidget(card)window.show()sys.exit(app.exec())
widget.py
# coding: utf-8
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayoutfrom card import AccordionCardclass AccordionWidget(QWidget):def __init__(self, orientation=Qt.Orientation.Horizontal, parent=None):super().__init__(parent)self._cards = []self._cardStretch = {}if orientation == Qt.Orientation.Horizontal:self.setLayout(QHBoxLayout())else:self.setLayout(QVBoxLayout())self.layout().setSpacing(5)def addCard(self, card: AccordionCard):card.setStretch(1.0)card.hovered.connect(self.onCardHovered)card.stretchChanged.connect(self.onCardStretchChanged)card.clicked.connect(lambda c=card: print(c, "Card clicked"))self._cards.append(card)self._cardStretch[card] = 1.0self.layout().addWidget(card)def onCardHovered(self, card: AccordionCard, hovered: bool):expanded_stretch = 5.0default_stretch = 1.0if hovered:for c in self._cards:if c == card:card.setTargetStretch(expanded_stretch)else:c.setTargetStretch(default_stretch)else:for c in self._cards:c.setTargetStretch(default_stretch)def onCardStretchChanged(self, card: AccordionCard, stretch: float):self._cardStretch[card] = stretchself.updateLayoutStretches()def updateLayoutStretches(self):if not self._cards:returnfor index, card in enumerate(self._cards):layout_stretch = int(self._cardStretch[card] * 1000)# print(f"Card {index} current stretch: {self._cardStretch[card]} layout stretch factor: {layout_stretch}")self.layout().setStretch(index, layout_stretch)
main.py
# coding: utf-8
from pathlib import Pathfrom PySide6.QtCore import Qtfrom card import AccordionCard
from widget import AccordionWidget
from PySide6.QtWidgets import QApplication, QMainWindowimport sysapp = QApplication(sys.argv)window = QMainWindow()
window.resize(800, 600)
accordion = AccordionWidget(Qt.Orientation.Vertical, parent=window)
for file in Path('./img').glob('*.jpg'):card = AccordionCard(file)accordion.addCard(card)
window.setCentralWidget(accordion)
window.setContentsMargins(20, 20, 20, 20)window.show()
sys.exit(app.exec())
运行
复制后,运行main.py
预览效果
项目参考
参考c++项目
gitee: https://gitee.com/chiyaun/Anime_carousel
github: https://github.com/daishuboluo/Anime_carousel.git