You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

适配多屏幕尺寸的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("下一步"),
            ),
          ],
        ),
      ),
    );
  }
}

关键适配技巧

  1. 小屏幕溢出处理:用SingleChildScrollView设置scrollDirection: Axis.horizontal,确保屏幕宽度不足时可以横向滚动,避免RenderFlex异常。
  2. 线条精准对齐:连接线用Expanded包裹的Container,高度设为2,Row的mainAxisAlignment设为center,容器会自动填充两个步骤圆圈之间的空间,且垂直居中对齐圆圈圆心。
  3. 多端响应式调整:
    • 步骤标题用固定宽度的SizedBox包裹,配合textAlign: TextAlign.center,避免文字过长导致布局混乱;
    • 可通过MediaQuery.of(context).size.width判断屏幕宽度,在Web端大屏幕时,动态增大圆圈和文字尺寸,小屏幕则缩小;
    • 若需要更极致的响应式,可在小屏幕时将步骤标题改为垂直排列,或者隐藏标题只显示圆圈。
  4. 状态样式统一:通过封装的颜色方法,确保已完成、当前、未完成状态的圆圈、线条、文字样式统一,避免代码冗余。

内容的提问来源于stack exchange,提问作者obs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 08:07:41