效果就是这样子
gif 麻烦,就截个图放这吧
代码
import sys, random
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QFont, QColor, QPen, QFontMetrics
class TypewriterAnimation(QWidget):
def __init__(self):
super().__init__()
# 窗口设置
self.setWindowTitle("打字机动画效果")
self.setGeometry(100, 100, 800, 600)
self.setStyleSheet("background-color: #1a1a1a;")
# 文字内容
self.text_lines = [
"欢迎来到打字机动画世界",
"每个字符都会从天而降",
"动画结束后会自动重播",
"PyQt5 让一切变得简单"
]
# 字体设置
self.font = QFont("微软雅黑", 24, QFont.Bold)
self.font_metrics = QFontMetrics(self.font) # 用于获取字体度量信息
# 动画参数
self.char_data = [] # 存储每个字符的信息
self.animation_speed = 3 # 下落速度
self.char_delay = 100 # 字符出现间隔(毫秒)
self.gravity = 0.5 # 重力加速度
# 文字间距设置(可调整)
self.char_spacing = 1.5 # 字符间距系数(1.0表示正常间距,大于1.0表示增加间距)
# 当前状态
self.current_line = 0
self.current_char = 0
self.animation_complete = False
self.replay_delay = 2000 # 重播延迟(毫秒)
# 定时器
self.char_timer = QTimer(self)
self.char_timer.timeout.connect(self.add_next_char)
self.char_timer.start(self.char_delay)
self.animation_timer = QTimer(self)
self.animation_timer.timeout.connect(self.update_animation)
self.animation_timer.start(16) # 约60 FPS
self.replay_timer = QTimer(self)
self.replay_timer.timeout.connect(self.reset_animation)
def add_next_char(self):
"""添加下一个字符到动画中"""
if self.current_line < len(self.text_lines):
line = self.text_lines[self.current_line]
if self.current_char < len(line):
char = line[self.current_char]
# 添加字符数据
char_info = {
'char': char,
'line_index': self.current_line, # 记录行号
'char_in_line': self.current_char, # 记录字符在行中的位置
'y': -50, # 从屏幕上方开始
'velocity': 0,
'color': QColor(
random.randint(100, 255),
random.randint(100, 255),
random.randint(100, 255)
),
'rotation': random.uniform(-30, 30), # 随机旋转角度
'rotation_speed': random.uniform(-5, 5), # 旋转速度
'scale': 0.1, # 初始缩放
'scale_speed': 0.05, # 缩放速度
'settled': False, # 是否已稳定
'settle_progress': 0.0 # 稳定进度(0-1)
}
self.char_data.append(char_info)
self.current_char += 1
else:
# 当前行完成,移到下一行
self.current_line += 1
self.current_char = 0
if self.current_line >= len(self.text_lines):
# 所有文字完成,停止添加字符
self.char_timer.stop()
self.animation_complete = True
# 设置重播定时器
self.replay_timer.start(self.replay_delay)
def update_animation(self):
"""更新动画状态"""
for char_info in self.char_data:
# 更新位置(重力效果)
if not char_info['settled']:
target_y = self.get_char_target_y(char_info)
if char_info['y'] < target_y:
char_info['velocity'] += self.gravity
char_info['y'] += char_info['velocity']
# 如果超过目标位置,回弹
if char_info['y'] > target_y:
char_info['y'] = target_y
char_info['velocity'] *= -0.5 # 弹性系数
# 如果速度很小,开始稳定过程
if abs(char_info['velocity']) < 0.5:
char_info['velocity'] = 0
char_info['settled'] = True
else:
char_info['y'] = target_y
char_info['settled'] = True
# 如果已稳定,开始恢复到正常状态
if char_info['settled'] and char_info['settle_progress'] < 1.0:
char_info['settle_progress'] += 0.05 # 稳定进度增加速度
if char_info['settle_progress'] > 1.0:
char_info['settle_progress'] = 1.0
# 逐渐恢复旋转角度
target_rotation = 0
char_info['rotation'] = char_info['rotation'] * (1 - char_info['settle_progress']) + \
target_rotation * char_info['settle_progress']
# 逐渐恢复缩放
target_scale = 1.0
char_info['scale'] = char_info['scale'] * (1 - char_info['settle_progress']) + \
target_scale * char_info['settle_progress']
else:
# 未稳定时的动画
if not char_info['settled']:
# 更新旋转
if abs(char_info['rotation_speed']) > 0.1:
char_info['rotation'] += char_info['rotation_speed']
char_info['rotation_speed'] *= 0.95 # 旋转衰减
# 更新缩放
if char_info['scale'] < 1.0:
char_info['scale'] += char_info['scale_speed']
if char_info['scale'] > 1.0:
char_info['scale'] = 1.0
self.update() # 触发重绘
def get_char_target_y(self, char_info):
"""获取字符的目标Y坐标"""
return 150 + char_info['line_index'] * 80
def get_char_x(self, char_info):
"""获取字符的X坐标(水平居中)"""
line = self.text_lines[char_info['line_index']]
# 计算整行文本的总宽度(考虑间距)
total_width = 0
for i, c in enumerate(line):
char_width = self.font_metrics.width(c)
if i < len(line) - 1: # 不是最后一个字符
total_width += char_width * self.char_spacing
else: # 最后一个字符不需要额外间距
total_width += char_width
# 计算起始位置(居中)
start_x = (self.width() - total_width) // 2
# 计算当前字符的位置
x = start_x
for i in range(char_info['char_in_line']):
char_width = self.font_metrics.width(line[i])
x += char_width * self.char_spacing
return x
def resizeEvent(self, event):
"""窗口大小改变时重新计算字符位置"""
super().resizeEvent(event)
# 窗口大小改变时,所有字符的位置需要重新计算
self.update()
def reset_animation(self):
"""重置动画"""
self.replay_timer.stop()
self.char_data.clear()
self.current_line = 0
self.current_char = 0
self.animation_complete = False
self.char_timer.start(self.char_delay)
def paintEvent(self, event):
"""绘制所有字符"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 绘制每个字符
for char_info in self.char_data:
painter.save()
# 设置字体
painter.setFont(self.font)
# 设置颜色
painter.setPen(QPen(char_info['color'], 2))
# 获取字符位置(动态计算,确保窗口大小变化时居中)
x = self.get_char_x(char_info)
y = char_info['y']
# 移动到字符位置
painter.translate(x, y)
# 应用旋转
painter.rotate(char_info['rotation'])
# 应用缩放
painter.scale(char_info['scale'], char_info['scale'])
# 绘制字符(居中绘制)
font_metrics = painter.fontMetrics()
char_width = font_metrics.width(char_info['char'])
char_height = font_metrics.height()
painter.drawText(
-char_width // 2,
char_height // 2,
char_info['char']
)
# 绘制阴影效果(仅在下落过程中)
if not char_info['settled']:
painter.setPen(QPen(QColor(255, 255, 255, 30), 1))
painter.drawText(
-char_width // 2 + 2,
char_height // 2 + 2,
char_info['char']
)
painter.restore()
# 绘制进度指示器
# if not self.animation_complete:
# painter.setPen(QPen(QColor(100, 100, 100), 2))
# painter.setFont(QFont("Arial", 10))
# progress_text = f"进度: {self.current_line + 1}/{len(self.text_lines)} 行"
# painter.drawText(10, self.height() - 10, progress_text)
# else:
# painter.setPen(QPen(QColor(255, 255, 0), 2))
# painter.setFont(QFont("Arial", 12))
# painter.drawText(
# self.width() // 2 - 50,
# self.height() - 20,
# "即将重播..."
# )
# 显示当前间距设置
# painter.setPen(QPen(QColor(150, 150, 150), 1))
# painter.setFont(QFont("Arial", 10))
# spacing_text = f"字符间距: {self.char_spacing:.1f}"
# painter.drawText(self.width() - 120, 20, spacing_text)
def main():
app = QApplication(sys.argv)
# 创建动画窗口
animation = TypewriterAnimation()
animation.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()