fix(mobile): 修复火柴人组件布局错误,避免嵌套Stack

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-24 02:53:21 -08:00
parent 0623169d19
commit 27bee6d7f1
1 changed files with 112 additions and 113 deletions

View File

@ -188,47 +188,55 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
builder: (context, constraints) { builder: (context, constraints) {
final containerWidth = constraints.maxWidth; final containerWidth = constraints.maxWidth;
return Stack( // Positioned widgets
clipBehavior: Clip.none, final List<Widget> children = [
children: [ // 线
// 线 Positioned.fill(
Positioned.fill( child: CustomPaint(
child: CustomPaint( painter: _TrackPainter(
painter: _TrackPainter( trackCount: sortedRankings.length,
trackCount: sortedRankings.length,
),
), ),
), ),
),
];
// //
...List.generate(trackCount, (index) { for (int index = 0; index < trackCount; index++) {
final verticalPosition = index * trackHeight + 10 + trackHeight / 2 - 16; final verticalPosition = index * trackHeight + 10 + trackHeight / 2 - 16;
return Positioned( children.add(Positioned(
right: 8, right: 8,
top: verticalPosition, top: verticalPosition,
child: const Icon( child: const Icon(
Icons.flag, Icons.flag,
color: Colors.red, color: Colors.red,
size: 24, size: 24,
), ),
); ));
}), }
// //
...sortedRankings.asMap().entries.map((entry) { for (int index = 0; index < sortedRankings.length; index++) {
final index = entry.key; final data = sortedRankings[index];
final data = entry.value; final widgets = _buildStickmanWidgets(data, index, sortedRankings.length, raceTrackHeight, containerWidth);
return _buildStickman(data, index, sortedRankings.length, raceTrackHeight, containerWidth); children.addAll(widgets);
}), }
],
return AnimatedBuilder(
animation: _bounceController,
builder: (context, child) {
return Stack(
clipBehavior: Clip.none,
children: children,
);
},
); );
}, },
), ),
); );
} }
/// /// Positioned widgets
Widget _buildStickman(StickmanRankingData data, int rank, int total, double raceTrackHeight, double containerWidth) { List<Widget> _buildStickmanWidgets(StickmanRankingData data, int rank, int total, double raceTrackHeight, double containerWidth) {
// (0.0 - 1.0) // (0.0 - 1.0)
final progress = data.progress; final progress = data.progress;
@ -237,100 +245,91 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
final trackHeight = usableHeight / total; final trackHeight = usableHeight / total;
final verticalPosition = rank * trackHeight + 10; final verticalPosition = rank * trackHeight + 10;
// 50maxWidth:60 // :
// : right=8, width=24, = containerWidth - 32 // = 70 ()
// : stickmanLeft + 50 = containerWidth - 32 // = - = (containerWidth - 32) - 50 = containerWidth - 82
// endX = containerWidth - 82
const double startX = 70.0; const double startX = 70.0;
final double endX = containerWidth - 82.0; final double endX = containerWidth - 82.0;
final double stickmanLeft = startX + (endX - startX) * progress; final double stickmanLeft = startX + (endX - startX) * progress;
return AnimatedBuilder( final bounce = _bounceController.value * 3;
animation: _bounceController,
builder: (context, child) {
final bounce = _bounceController.value * 3;
return Stack( return [
clipBehavior: Clip.none, // -
Positioned(
left: 0,
top: verticalPosition + 15 - bounce,
child: SizedBox(
width: 65,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: data.isCurrentUser
? const Color(0xFFD4AF37).withValues(alpha: 0.2)
: Colors.white.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(8),
border: data.isCurrentUser
? Border.all(color: const Color(0xFFD4AF37), width: 1)
: null,
),
child: Text(
data.nickname,
style: TextStyle(
fontSize: 9,
fontFamily: 'Inter',
fontWeight: data.isCurrentUser ? FontWeight.w600 : FontWeight.w400,
color: data.isCurrentUser
? const Color(0xFFD4AF37)
: const Color(0xFF5D4037),
),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
),
),
//
Positioned(
left: stickmanLeft,
top: verticalPosition - bounce,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
// - //
Positioned( Container(
left: 0, constraints: const BoxConstraints(maxWidth: 60),
top: verticalPosition + 15 - bounce, padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
child: SizedBox( decoration: BoxDecoration(
width: 65, color: data.isCurrentUser
child: Container( ? const Color(0xFFD4AF37)
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), : const Color(0xFF8B5A2B),
decoration: BoxDecoration( borderRadius: BorderRadius.circular(8),
color: data.isCurrentUser ),
? const Color(0xFFD4AF37).withValues(alpha: 0.2) child: FittedBox(
: Colors.white.withValues(alpha: 0.8), fit: BoxFit.scaleDown,
borderRadius: BorderRadius.circular(8), child: Text(
border: data.isCurrentUser '${_formatNumber(data.completedCount)}',
? Border.all(color: const Color(0xFFD4AF37), width: 1) style: const TextStyle(
: null, fontSize: 9,
), fontFamily: 'Inter',
child: Text( fontWeight: FontWeight.w600,
data.nickname, color: Colors.white,
style: TextStyle(
fontSize: 9,
fontFamily: 'Inter',
fontWeight: data.isCurrentUser ? FontWeight.w600 : FontWeight.w400,
color: data.isCurrentUser
? const Color(0xFFD4AF37)
: const Color(0xFF5D4037),
),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
), ),
), ),
), ),
), ),
// const SizedBox(height: 2),
Positioned( //
left: stickmanLeft, RunningStickman(
top: verticalPosition - bounce, size: 36,
child: Column( color: data.isCurrentUser
mainAxisSize: MainAxisSize.min, ? const Color(0xFFD4AF37)
children: [ : const Color(0xFF5D4037),
//
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),
),
],
),
), ),
], ],
); ),
}, ),
); ];
} }
/// ///