feat(mobile-app): 使用 CustomPaint 绘制跑步火柴人动画
替换 Lottie 动画为自定义绘制的跑步火柴人: - 使用 CustomPaint 绘制火柴人形状 - 添加腿部和手臂摆动动画 - 添加速度线效果 - 当前用户显示金色,其他用户显示棕色 - 移除 lottie 依赖 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
770bcb85a2
commit
e275724359
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
/// 火柴人排名数据模型
|
||||
class StickmanRankingData {
|
||||
|
|
@ -267,14 +266,11 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
|
|||
const SizedBox(height: 2),
|
||||
|
||||
// 火柴人动画
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 50,
|
||||
child: Lottie.asset(
|
||||
'assets/lottie/stickman_running.json',
|
||||
fit: BoxFit.contain,
|
||||
repeat: true,
|
||||
),
|
||||
RunningStickman(
|
||||
size: 36,
|
||||
color: data.isCurrentUser
|
||||
? const Color(0xFFD4AF37)
|
||||
: const Color(0xFF5D4037),
|
||||
),
|
||||
|
||||
// 昵称标签
|
||||
|
|
@ -592,3 +588,202 @@ class _TrackPainter extends CustomPainter {
|
|||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
/// 跑步火柴人动画组件
|
||||
class RunningStickman extends StatefulWidget {
|
||||
final double size;
|
||||
final Color color;
|
||||
|
||||
const RunningStickman({
|
||||
super.key,
|
||||
this.size = 40,
|
||||
this.color = Colors.black,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RunningStickman> createState() => _RunningStickmanState();
|
||||
}
|
||||
|
||||
class _RunningStickmanState extends State<RunningStickman>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
vsync: this,
|
||||
)..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
size: Size(widget.size, widget.size * 1.2),
|
||||
painter: _RunningStickmanPainter(
|
||||
progress: _controller.value,
|
||||
color: widget.color,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 跑步火柴人绘制器
|
||||
class _RunningStickmanPainter extends CustomPainter {
|
||||
final double progress;
|
||||
final Color color;
|
||||
|
||||
_RunningStickmanPainter({
|
||||
required this.progress,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = size.width * 0.08
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final fillPaint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
// 尺寸计算
|
||||
final centerX = size.width * 0.5;
|
||||
final headRadius = size.width * 0.18;
|
||||
final headY = size.height * 0.15;
|
||||
|
||||
// 身体各部位位置
|
||||
final neckY = headY + headRadius;
|
||||
final shoulderY = size.height * 0.35;
|
||||
final hipY = size.height * 0.55;
|
||||
final bodyX = centerX;
|
||||
|
||||
// 动画相位 (0-1 循环)
|
||||
final phase = progress * 2 * 3.14159;
|
||||
|
||||
// 腿部摆动幅度
|
||||
final legSwing = size.width * 0.35 * (phase < 3.14159 ?
|
||||
(phase / 3.14159) * 2 - 1 :
|
||||
1 - ((phase - 3.14159) / 3.14159) * 2);
|
||||
|
||||
// 手臂摆动(与腿相反)
|
||||
final armSwing = -legSwing * 0.8;
|
||||
|
||||
// 绘制头部
|
||||
canvas.drawCircle(
|
||||
Offset(centerX, headY),
|
||||
headRadius,
|
||||
fillPaint,
|
||||
);
|
||||
|
||||
// 绘制身体
|
||||
canvas.drawLine(
|
||||
Offset(bodyX, neckY),
|
||||
Offset(bodyX, hipY),
|
||||
paint,
|
||||
);
|
||||
|
||||
// 绘制速度线(在身体左侧)
|
||||
final speedLinePaint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = size.width * 0.05
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final lineY = shoulderY + i * size.height * 0.08;
|
||||
final lineStartX = centerX - size.width * 0.5;
|
||||
final lineEndX = lineStartX + size.width * 0.2;
|
||||
canvas.drawLine(
|
||||
Offset(lineStartX, lineY),
|
||||
Offset(lineEndX, lineY),
|
||||
speedLinePaint,
|
||||
);
|
||||
}
|
||||
|
||||
// 绘制手臂
|
||||
// 后手臂
|
||||
final backArmEndX = bodyX - size.width * 0.25 + armSwing * 0.5;
|
||||
final backArmEndY = shoulderY + size.height * 0.15;
|
||||
canvas.drawLine(
|
||||
Offset(bodyX, shoulderY),
|
||||
Offset(backArmEndX, backArmEndY),
|
||||
paint,
|
||||
);
|
||||
// 后手臂前臂
|
||||
canvas.drawLine(
|
||||
Offset(backArmEndX, backArmEndY),
|
||||
Offset(backArmEndX + size.width * 0.15, backArmEndY - size.height * 0.1),
|
||||
paint,
|
||||
);
|
||||
|
||||
// 前手臂
|
||||
final frontArmEndX = bodyX + size.width * 0.2 - armSwing * 0.5;
|
||||
final frontArmEndY = shoulderY + size.height * 0.12;
|
||||
canvas.drawLine(
|
||||
Offset(bodyX, shoulderY),
|
||||
Offset(frontArmEndX, frontArmEndY),
|
||||
paint,
|
||||
);
|
||||
// 前手臂前臂
|
||||
canvas.drawLine(
|
||||
Offset(frontArmEndX, frontArmEndY),
|
||||
Offset(frontArmEndX + size.width * 0.1, frontArmEndY - size.height * 0.08),
|
||||
paint,
|
||||
);
|
||||
|
||||
// 绘制腿部
|
||||
final footY = size.height * 0.95;
|
||||
|
||||
// 后腿
|
||||
final backKneeX = bodyX - size.width * 0.1 - legSwing * 0.3;
|
||||
final backKneeY = hipY + size.height * 0.2;
|
||||
final backFootX = bodyX - size.width * 0.3 + legSwing * 0.5;
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(bodyX, hipY),
|
||||
Offset(backKneeX, backKneeY),
|
||||
paint,
|
||||
);
|
||||
canvas.drawLine(
|
||||
Offset(backKneeX, backKneeY),
|
||||
Offset(backFootX, footY),
|
||||
paint,
|
||||
);
|
||||
|
||||
// 前腿
|
||||
final frontKneeX = bodyX + size.width * 0.15 + legSwing * 0.3;
|
||||
final frontKneeY = hipY + size.height * 0.18;
|
||||
final frontFootX = bodyX + size.width * 0.35 - legSwing * 0.5;
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(bodyX, hipY),
|
||||
Offset(frontKneeX, frontKneeY),
|
||||
paint,
|
||||
);
|
||||
canvas.drawLine(
|
||||
Offset(frontKneeX, frontKneeY),
|
||||
Offset(frontFootX, footY),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _RunningStickmanPainter oldDelegate) {
|
||||
return oldDelegate.progress != progress;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue