import math import random from PyQt5.QtCore import Qt, QPropertyAnimation, QRectF, QPoint, QTimer, QParallelAnimationGroup, pyqtSignal, QPointF, \ pyqtProperty, QEasingCurve from PyQt5.QtGui import QPixmap, QIcon, QColor, QPainter, QLinearGradient, QBrush, QRadialGradient, QConicalGradient from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QGridLayout, QSpacerItem, QSizePolicy) class AnimatedCard(QFrame): """具有动画效果的卡片控件""" def __init__(self, start_color, end_color, parent=None): super().__init__(parent) self.start_color = QColor(start_color) self.end_color = QColor(end_color) self.angle = 0 self.hover_state = False self.init_animations() self.setMinimumSize(300, 280) self.setMaximumSize(300, 280) self.setCursor(Qt.PointingHandCursor) self.setStyleSheet(""" AnimatedCard { border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.1); transition: all 0.3s ease; } """) def init_animations(self): # 渐变角度动画 self.angle_animation = QPropertyAnimation(self, b"angle") self.angle_animation.setDuration(20000) # 20秒完成一轮 self.angle_animation.setStartValue(0) self.angle_animation.setEndValue(360) self.angle_animation.setLoopCount(-1) # 无限循环 self.angle_animation.start() # 悬停动画组 self.hover_animation = QPropertyAnimation(self, b"geometry") self.hover_animation.setDuration(300) self.hover_animation.setEasingCurve(QEasingCurve.OutBack) # 阴影动画 self.shadow_animation = QPropertyAnimation(self, b"shadow") self.shadow_animation.setDuration(300) # 并行动画组 self.animation_group = QParallelAnimationGroup(self) self.animation_group.addAnimation(self.hover_animation) self.animation_group.addAnimation(self.shadow_animation) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) # 创建主渐变背景 rect = self.rect() # 添加动态渐变背景 if self.hover_state: # 悬停状态使用更亮的渐变 gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) gradient.setColorAt(0, self.start_color.lighter(120)) gradient.setColorAt(1, self.end_color.lighter(120)) else: # 普通状态 gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) gradient.setColorAt(0, self.start_color) gradient.setColorAt(1, self.end_color) # 添加动态旋转效果 cx = rect.center().x() cy = rect.center().y() radius = max(rect.width(), rect.height()) * 1.2 start_x = cx + radius * 0.5 * math.cos(math.radians(self.angle)) start_y = cy + radius * 0.5 * math.sin(math.radians(self.angle)) end_x = cx - radius * 0.5 * math.cos(math.radians(self.angle)) end_y = cy - radius * 0.5 * math.sin(math.radians(self.angle)) moving_gradient = QLinearGradient(start_x, start_y, end_x, end_y) moving_gradient.setColorAt(0, QColor(255, 255, 255, 30)) moving_gradient.setColorAt(1, QColor(255, 255, 255, 10)) # 绘制背景 painter.setBrush(gradient) painter.drawRoundedRect(rect, 16, 16) # 绘制动态效果层 painter.setBrush(moving_gradient) painter.drawRoundedRect(rect, 16, 16) # 绘制高光边框 if self.hover_state: border_gradient = QLinearGradient(rect.topLeft(), rect.topRight()) border_gradient.setColorAt(0, QColor(255, 255, 255, 100)) border_gradient.setColorAt(1, QColor(255, 255, 255, 30)) painter.setPen(QColor(255, 255, 255, 150)) painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 16, 16) def enterEvent(self, event): self.hover_state = True self.animate_hover(True) self.update() super().enterEvent(event) def leaveEvent(self, event): self.hover_state = False self.animate_hover(False) self.update() super().leaveEvent(event) def animate_hover(self, hover): """执行悬停动画""" # 保存当前位置和大小 current_geometry = self.geometry() # 目标位置和大小 if hover: target_geometry = current_geometry.adjusted(-5, -5, 5, 5) shadow_value = 20 else: target_geometry = current_geometry.adjusted(5, 5, -5, -5) shadow_value = 5 # 设置位置动画 self.hover_animation.setStartValue(current_geometry) self.hover_animation.setEndValue(target_geometry) # 设置阴影动画 self.shadow_animation.setStartValue(self.styleSheet()) self.shadow_animation.setEndValue(f""" AnimatedCard {{ border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 10px {shadow_value}px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; }} """) # 开始动画 self.animation_group.start() @pyqtProperty(int) def angle(self): return self._angle @angle.setter def angle(self, value): self._angle = value self.update() class Home(QWidget): app_selected = pyqtSignal(str) def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setObjectName("appCenter") self.setStyleSheet(self.get_styles()) layout = QVBoxLayout() layout.setContentsMargins(40, 30, 40, 30) layout.setSpacing(20) # 标题区域 title_layout = QHBoxLayout() title = QLabel("首页") title.setObjectName("pageTitle") title_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) title_layout.addWidget(title) title_layout.addSpacerItem(title_spacer) layout.addLayout(title_layout) # 卡片容器 grid_layout = QVBoxLayout() grid_layout.setSpacing(30) # 应用卡片 apps = [ { "id": "word_to_pdf", "title": "Word转PDF", "desc": "批量将Word文档转换为PDF格式", "color": ("#6366F1", "#4F46E5"), # 紫色 "icon": "word_to_pdf" }, { "id": "ocr", "title": "OCR文字识别", "desc": "从图片中提取文字内容", "color": ("#4ADE80", "#22C55E"), # 绿色 "icon": "ocr" }, { "id": "image_convert", "title": "图片格式转换", "desc": "转换图像文件格式并优化", "color": ("#F97316", "#EA580C"), # 橙色 "icon": "image_convert" }, { "id": "pdf_merge", "title": "PDF合并工具", "desc": "合并多个PDF文件为一个", "color": ("#8B5CF6", "#7C3AED"), # 紫色 "icon": "pdf_merge" }, { "id": "pdf_split", "title": "PDF拆分工具", "desc": "拆分PDF文件为多个文档", "color": ("#EC4899", "#DB2777"), # 粉色 "icon": "pdf_split" }, { "id": "video_compress", "title": "视频压缩", "desc": "减小视频文件大小", "color": ("#3B82F6", "#2563EB"), # 蓝色 "icon": "video_compress" } ] # 每行显示3张卡片 for i in range(0, len(apps), 3): row_layout = QHBoxLayout() row_layout.setSpacing(30) row_layout.setAlignment(Qt.AlignCenter) for j in range(3): index = i + j if index < len(apps): row_layout.addWidget(self.create_app_card(apps[index])) else: # 添加空白占位符保持布局 spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) row_layout.addSpacerItem(spacer) grid_layout.addLayout(row_layout) layout.addLayout(grid_layout) layout.addStretch() self.setLayout(layout) # 添加背景粒子效果 self.particles = [] self.init_particles() self.particle_timer = QTimer(self) self.particle_timer.timeout.connect(self.update_particles) self.particle_timer.start(30) # 30ms更新一次 def get_styles(self): return """ /* ===== 应用中心样式 ===== */ #appCenter { background-color: #1a202c; } #pageTitle { font-size: 28px; font-weight: bold; color: #ffffff; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); margin-bottom: 30px; } #appCardTitle { font-size: 22px; font-weight: 700; color: white; text-align: center; margin-top: 15px; letter-spacing: 0.5px; } #appCardDesc { font-size: 15px; color: rgba(255, 255, 255, 0.8); text-align: center; padding: 0 15px; line-height: 1.5; } #appCardBtn { background-color: rgba(255, 255, 255, 0.9); color: #1a202c; border-radius: 12px; padding: 12px 24px; font-weight: 600; border: none; min-width: 140px; transition: all 0.3s ease; font-size: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } #appCardBtn:hover { background-color: white; transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } #appCardIcon { background-color: rgba(255, 255, 255, 0.15); border-radius: 50%; transition: all 0.3s ease; } """ def create_app_card(self, app): """创建应用卡片""" card = AnimatedCard(app["color"][0], app["color"][1]) card.setObjectName("appCard") layout = QVBoxLayout(card) layout.setContentsMargins(25, 25, 25, 25) layout.setSpacing(15) # 图标 icon_frame = QFrame() icon_frame.setFixedSize(90, 90) icon_frame.setObjectName("appCardIcon") icon_layout = QVBoxLayout(icon_frame) icon_layout.setContentsMargins(0, 0, 0, 0) icon = QLabel() icon_pixmap = QPixmap(f":/icons/{app['icon']}.png").scaled(56, 56, Qt.KeepAspectRatio, Qt.SmoothTransformation) # 转换图标为白色 painter = QPainter(icon_pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(icon_pixmap.rect(), Qt.white) painter.end() icon.setPixmap(icon_pixmap) icon.setAlignment(Qt.AlignCenter) icon_layout.addWidget(icon) layout.addWidget(icon_frame, 0, Qt.AlignHCenter) # 标题 title = QLabel(app["title"]) title.setObjectName("appCardTitle") layout.addWidget(title) # 描述 desc = QLabel(app["desc"]) desc.setObjectName("appCardDesc") desc.setWordWrap(True) layout.addWidget(desc) # 添加伸缩空间保持底部对齐 layout.addStretch() # 按钮 btn_text = "开始使用" if app["id"] != "coming_soon" else "敬请期待" btn = QPushButton(btn_text) btn.setObjectName("appCardBtn") btn.setCursor(Qt.PointingHandCursor) if app["id"] == "coming_soon": btn.setEnabled(False) btn.setStyleSheet(""" #appCardBtn:disabled { background-color: rgba(255, 255, 255, 0.3); color: rgba(26, 32, 44, 0.7); } """) else: btn.clicked.connect(lambda: self.app_selected.emit(app["id"])) layout.addWidget(btn, 0, Qt.AlignHCenter) # 为卡片添加点击事件 card.mousePressEvent = lambda e: self.app_selected.emit(app["id"]) if app["id"] != "coming_soon" else None return card def init_particles(self): """初始化背景粒子""" for _ in range(20): particle = { 'x': random.randint(0, self.width()), 'y': random.randint(0, self.height()), 'size': random.randint(2, 6), 'speed': random.uniform(0.5, 2), 'color': random.choice([ QColor(79, 70, 229, 80), # 紫色 QColor(129, 140, 248, 60), # 浅紫 QColor(59, 130, 246, 70), # 蓝色 QColor(16, 185, 129, 60) # 绿色 ]) } self.particles.append(particle) def update_particles(self): """更新粒子位置""" for particle in self.particles: particle['y'] += particle['speed'] if particle['y'] > self.height(): particle['y'] = 0 particle['x'] = random.randint(0, self.width()) self.update() def paintEvent(self, event): """绘制背景粒子效果""" super().paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) # 绘制粒子 for particle in self.particles: painter.setBrush(particle['color']) painter.drawEllipse(QPointF(particle['x'], particle['y']), particle['size'], particle['size'])