revert(mobile): 回滚火柴人组件到稳定版本
🤖 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
27bee6d7f1
commit
e2872b13fb
|
|
@ -8,7 +8,7 @@ class StickmanRankingData {
|
||||||
final String nickname;
|
final String nickname;
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
final int completedCount; // 完成数量
|
final int completedCount; // 完成数量
|
||||||
final int targetCount; // 目标数量 (省5万/市1万)
|
final int targetCount; // 目标数量 (省: 50000, 市: 10000)
|
||||||
final double monthlyEarnings; // 本月可结算收益
|
final double monthlyEarnings; // 本月可结算收益
|
||||||
final bool isCurrentUser; // 是否是当前用户
|
final bool isCurrentUser; // 是否是当前用户
|
||||||
final String? accountSequence; // 账户序列号(用于查看详情)
|
final String? accountSequence; // 账户序列号(用于查看详情)
|
||||||
|
|
@ -25,10 +25,7 @@ class StickmanRankingData {
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 完成进度 (0.0 - 1.0)
|
/// 完成进度 (0.0 - 1.0)
|
||||||
/// 直接用 completedCount / targetCount 计算,达到或超过目标就是100%
|
double get progress => (completedCount / targetCount).clamp(0.0, 1.0);
|
||||||
double get progress => targetCount > 0
|
|
||||||
? (completedCount / targetCount).clamp(0.0, 1.0)
|
|
||||||
: 0.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 授权类型
|
/// 授权类型
|
||||||
|
|
@ -184,26 +181,21 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: raceTrackHeight,
|
height: raceTrackHeight,
|
||||||
child: LayoutBuilder(
|
child: Stack(
|
||||||
builder: (context, constraints) {
|
children: [
|
||||||
final containerWidth = constraints.maxWidth;
|
// 背景赛道线
|
||||||
|
Positioned.fill(
|
||||||
// 收集所有 Positioned widgets
|
child: CustomPaint(
|
||||||
final List<Widget> children = [
|
painter: _TrackPainter(
|
||||||
// 背景赛道线
|
trackCount: sortedRankings.length,
|
||||||
Positioned.fill(
|
|
||||||
child: CustomPaint(
|
|
||||||
painter: _TrackPainter(
|
|
||||||
trackCount: sortedRankings.length,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
),
|
||||||
|
|
||||||
// 添加每个跑道的终点红旗
|
// 每个跑道的终点红旗
|
||||||
for (int index = 0; index < trackCount; index++) {
|
...List.generate(trackCount, (index) {
|
||||||
final verticalPosition = index * trackHeight + 10 + trackHeight / 2 - 16;
|
final verticalPosition = index * trackHeight + 10 + trackHeight / 2 - 16;
|
||||||
children.add(Positioned(
|
return Positioned(
|
||||||
right: 8,
|
right: 8,
|
||||||
top: verticalPosition,
|
top: verticalPosition,
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
|
|
@ -211,125 +203,134 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}
|
}),
|
||||||
|
|
||||||
// 添加火柴人们(昵称和火柴人分开添加)
|
// 火柴人们
|
||||||
for (int index = 0; index < sortedRankings.length; index++) {
|
...sortedRankings.asMap().entries.map((entry) {
|
||||||
final data = sortedRankings[index];
|
final index = entry.key;
|
||||||
final widgets = _buildStickmanWidgets(data, index, sortedRankings.length, raceTrackHeight, containerWidth);
|
final data = entry.value;
|
||||||
children.addAll(widgets);
|
return _buildStickman(data, index, sortedRankings.length, raceTrackHeight);
|
||||||
}
|
}),
|
||||||
|
],
|
||||||
return AnimatedBuilder(
|
|
||||||
animation: _bounceController,
|
|
||||||
builder: (context, child) {
|
|
||||||
return Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: children,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建单个火柴人的所有 Positioned widgets
|
/// 构建单个火柴人
|
||||||
List<Widget> _buildStickmanWidgets(StickmanRankingData data, int rank, int total, double raceTrackHeight, double containerWidth) {
|
Widget _buildStickman(StickmanRankingData data, int rank, int total, double raceTrackHeight) {
|
||||||
// 进度 (0.0 - 1.0)
|
// 计算水平位置 (根据进度)
|
||||||
final progress = data.progress;
|
final horizontalProgress = data.progress;
|
||||||
|
|
||||||
// 计算垂直位置 (不同排名在不同跑道)
|
// 计算垂直位置 (不同排名在不同跑道)
|
||||||
|
// 预留顶部和底部空间
|
||||||
final usableHeight = raceTrackHeight - 40;
|
final usableHeight = raceTrackHeight - 40;
|
||||||
final trackHeight = usableHeight / total;
|
final trackHeight = usableHeight / total;
|
||||||
final verticalPosition = rank * trackHeight + 10;
|
final verticalPosition = rank * trackHeight + 10;
|
||||||
|
|
||||||
// 简单计算:
|
return AnimatedBuilder(
|
||||||
// 起点 = 70 (昵称区域右边)
|
animation: _bounceController,
|
||||||
// 终点 = 红旗左边位置 - 火柴人宽度 = (containerWidth - 32) - 50 = containerWidth - 82
|
builder: (context, child) {
|
||||||
const double startX = 70.0;
|
// 添加上下弹跳效果
|
||||||
final double endX = containerWidth - 82.0;
|
final bounce = _bounceController.value * 3;
|
||||||
final double stickmanLeft = startX + (endX - startX) * progress;
|
|
||||||
|
|
||||||
final bounce = _bounceController.value * 3;
|
// 计算可用宽度:屏幕宽度 - 页面padding(32) - 容器padding(32) - 红旗区域(40) - 火柴人宽度一半(30)
|
||||||
|
// 让火柴人在100%时正好到达红旗位置
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final containerWidth = screenWidth - 32 - 32; // 页面padding + 容器padding
|
||||||
|
final stickmanHalfWidth = 30.0; // 火柴人宽度的一半,用于居中对齐
|
||||||
|
final flagAreaWidth = 40.0; // 红旗区域宽度(红旗在right:8位置,图标24+边距)
|
||||||
|
final nicknameAreaWidth = 65.0; // 昵称区域宽度
|
||||||
|
final availableWidth = containerWidth - flagAreaWidth - stickmanHalfWidth - nicknameAreaWidth;
|
||||||
|
final leftPosition = nicknameAreaWidth + availableWidth * horizontalProgress;
|
||||||
|
|
||||||
return [
|
return Positioned(
|
||||||
// 昵称标签 - 固定在左边
|
left: 0,
|
||||||
Positioned(
|
top: verticalPosition - bounce,
|
||||||
left: 0,
|
child: Row(
|
||||||
top: verticalPosition + 15 - bounce,
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: SizedBox(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
width: 65,
|
children: [
|
||||||
child: Container(
|
// 昵称标签 - 固定在左边
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
SizedBox(
|
||||||
decoration: BoxDecoration(
|
width: nicknameAreaWidth,
|
||||||
color: data.isCurrentUser
|
child: Container(
|
||||||
? const Color(0xFFD4AF37).withValues(alpha: 0.2)
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||||
: Colors.white.withValues(alpha: 0.8),
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: data.isCurrentUser
|
||||||
border: data.isCurrentUser
|
? const Color(0xFFD4AF37).withValues(alpha: 0.2)
|
||||||
? Border.all(color: const Color(0xFFD4AF37), width: 1)
|
: Colors.white.withValues(alpha: 0.8),
|
||||||
: null,
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
border: data.isCurrentUser
|
||||||
child: Text(
|
? Border.all(color: const Color(0xFFD4AF37), width: 1)
|
||||||
data.nickname,
|
: null,
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 9,
|
child: Text(
|
||||||
fontFamily: 'Inter',
|
data.nickname,
|
||||||
fontWeight: data.isCurrentUser ? FontWeight.w600 : FontWeight.w400,
|
style: TextStyle(
|
||||||
color: data.isCurrentUser
|
fontSize: 9,
|
||||||
? const Color(0xFFD4AF37)
|
fontFamily: 'Inter',
|
||||||
: const Color(0xFF5D4037),
|
fontWeight:
|
||||||
),
|
data.isCurrentUser ? FontWeight.w600 : FontWeight.w400,
|
||||||
overflow: TextOverflow.ellipsis,
|
color: data.isCurrentUser
|
||||||
textAlign: TextAlign.center,
|
? const Color(0xFFD4AF37)
|
||||||
),
|
: const Color(0xFF5D4037),
|
||||||
),
|
),
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
// 火柴人和数量标签
|
|
||||||
Positioned(
|
|
||||||
left: stickmanLeft,
|
|
||||||
top: verticalPosition - bounce,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
// 完成数量标签
|
|
||||||
Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 60),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: data.isCurrentUser
|
|
||||||
? const Color(0xFFD4AF37)
|
|
||||||
: const Color(0xFF8B5A2B),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child: Text(
|
|
||||||
'${_formatNumber(data.completedCount)}棵',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 9,
|
|
||||||
fontFamily: 'Inter',
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// 火柴人和数量标签
|
||||||
const SizedBox(height: 2),
|
SizedBox(
|
||||||
// 火柴人动画
|
width: leftPosition - nicknameAreaWidth + stickmanHalfWidth * 2,
|
||||||
RunningStickman(
|
child: Row(
|
||||||
size: 36,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
color: data.isCurrentUser
|
children: [
|
||||||
? const Color(0xFFD4AF37)
|
Column(
|
||||||
: const Color(0xFF5D4037),
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: [
|
||||||
],
|
// 完成数量标签
|
||||||
),
|
Container(
|
||||||
),
|
constraints: const BoxConstraints(maxWidth: 60),
|
||||||
];
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: data.isCurrentUser
|
||||||
|
? const Color(0xFFD4AF37)
|
||||||
|
: const Color(0xFF8B5A2B),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
'${_formatNumber(data.completedCount)}棵',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 9,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
// 火柴人动画
|
||||||
|
RunningStickman(
|
||||||
|
size: 36,
|
||||||
|
color: data.isCurrentUser
|
||||||
|
? const Color(0xFFD4AF37)
|
||||||
|
: const Color(0xFF5D4037),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建进度条
|
/// 构建进度条
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue