Qt小组件 - 1(手风琴)

发布于:2025-07-02 ⋅ 阅读:(19) ⋅ 点赞:(0)

代码

card.py

# coding: utf-8
import sys
from pathlib import Path
from typing import Union

from 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, QMainWindow


class 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.0
        self.__radius = 5.0
        self.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 = image

    def getStretch(self) -> float:
        return self.__stretch

    def setStretch(self, stretch: float):
        if self.__stretch == stretch:
            return
        self.__stretch = stretch
        self.updateGeometry()

    def setRadius(self, radius: float):
        self.__radius = radius
        self.update()

    def getRadius(self) -> float:
        return self.__radius

    def 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: QImage

        iw, ih = image.width(), image.height()
        w = self.width() * self.devicePixelRatioF()
        h = self.height() * self.devicePixelRatioF()
        x, y = (iw - w) / 2, (ih - h) / 2
        image = 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, QVBoxLayout

from card import AccordionCard


class 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.0
        self.layout().addWidget(card)

    def onCardHovered(self, card: AccordionCard, hovered: bool):
        expanded_stretch = 5.0
        default_stretch = 1.0
        if 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] = stretch
        self.updateLayoutStretches()

    def updateLayoutStretches(self):
        if not self._cards:
            return

        for 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 Path

from PySide6.QtCore import Qt

from card import AccordionCard
from widget import AccordionWidget
from PySide6.QtWidgets import QApplication, QMainWindow

import sys

app = 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


网站公告

今日签到

点亮在社区的每一天
去签到