适配多屏幕尺寸的Flutter类Stepper UI实现求助
Flutter 适配多端的步骤概览UI实现方案
核心实现思路
要解决多端适配和UI对齐问题,核心是横向滚动兼容+弹性布局分配空间+精准的线条对齐:
- 用
SingleChildScrollView包裹横向布局,解决小屏幕溢出问题; - 每个步骤项通过
Expanded均分宽度,确保大屏幕上布局均匀; - 步骤间的连接线用
Expanded的容器实现,自动填充间距且对齐圆圈圆心; - 基于步骤状态(已完成/当前/未完成)动态调整样式。
完整可复用代码
import 'package:flutter/material.dart'; class StepIndicator extends StatelessWidget { final List<String> stepTitles; final int currentStep; final Function(int) onStepTap; const StepIndicator({ super.key, required this.stepTitles, required this.currentStep, required this.onStepTap, }); @override Widget build(BuildContext context) { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(stepTitles.length, (index) { return Row( children: [ GestureDetector( onTap: () => onStepTap(index), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 步骤圆圈 Container( width: 40, height: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: _getCircleColor(index), border: Border.all( color: _getCircleBorderColor(index), width: 2, ), ), child: Center( child: Text( index < currentStep ? "✓" : "${index + 1}", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16, ), ), ), ), const SizedBox(height: 8), // 步骤标题 SizedBox( width: 80, child: Text( stepTitles[index], textAlign: TextAlign.center, style: TextStyle( fontSize: 12, fontWeight: index == currentStep ? FontWeight.bold : FontWeight.normal, color: _getTextColor(index), ), ), ), ], ), ), // 步骤间连接线(最后一个步骤不加) if (index != stepTitles.length - 1) Expanded( child: Container( height: 2, color: _getLineColor(index), ), ), ], ); }), ), ); } Color _getCircleColor(int index) { if (index < currentStep) return Colors.green; if (index == currentStep) return Colors.blue; return Colors.grey.shade300; } Color _getCircleBorderColor(int index) { if (index < currentStep) return Colors.green; if (index == currentStep) return Colors.blue; return Colors.grey.shade400; } Color _getLineColor(int index) { return index < currentStep ? Colors.green : Colors.grey.shade300; } Color _getTextColor(int index) { if (index < currentStep) return Colors.green; if (index == currentStep) return Colors.blue; return Colors.grey.shade600; } } // 使用示例 class StepDemo extends StatefulWidget { const StepDemo({super.key}); @override State<StepDemo> createState() => _StepDemoState(); } class _StepDemoState extends State<StepDemo> { int _currentStep = 0; final List<String> _steps = ["个人信息", "地址填写", "确认订单", "支付完成"]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("步骤概览Demo")), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ StepIndicator( stepTitles: _steps, currentStep: _currentStep, onStepTap: (index) => setState(() => _currentStep = index), ), const SizedBox(height: 40), ElevatedButton( onPressed: _currentStep < _steps.length - 1 ? () => setState(() => _currentStep++) : null, child: const Text("下一步"), ), ], ), ), ); } }
关键适配技巧
- 小屏幕溢出处理:用
SingleChildScrollView设置scrollDirection: Axis.horizontal,确保屏幕宽度不足时可以横向滚动,避免RenderFlex异常。 - 线条精准对齐:连接线用
Expanded包裹的Container,高度设为2,Row的mainAxisAlignment设为center,容器会自动填充两个步骤圆圈之间的空间,且垂直居中对齐圆圈圆心。 - 多端响应式调整:
- 步骤标题用固定宽度的
SizedBox包裹,配合textAlign: TextAlign.center,避免文字过长导致布局混乱; - 可通过
MediaQuery.of(context).size.width判断屏幕宽度,在Web端大屏幕时,动态增大圆圈和文字尺寸,小屏幕则缩小; - 若需要更极致的响应式,可在小屏幕时将步骤标题改为垂直排列,或者隐藏标题只显示圆圈。
- 步骤标题用固定宽度的
- 状态样式统一:通过封装的颜色方法,确保已完成、当前、未完成状态的圆圈、线条、文字样式统一,避免代码冗余。
内容的提问来源于stack exchange,提问作者obs
相关产品推荐
相关产品推荐

