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

Flutter多页面表单实现咨询:用顶部linear progress indicator替代Stepper widget

实现带顶部线性进度条的多页面表单方案

针对你的需求,这里提供一套基于Flutter的实现方案,完全替代Stepper组件,满足独立页面、数据收集、进度展示和步骤编辑的要求:

核心思路

用一个父组件统一管理当前步骤索引、全表单数据和总步骤数,每个步骤对应独立的页面组件;顶部通过LinearProgressIndicator计算进度;添加可点击的步骤指示器支持直接跳转到任意步骤编辑,同时确保所有步骤的数据能被统一收集和提交。

分步实现代码示例

1. 定义表单数据模型

先创建一个类来存储所有步骤的填写数据,方便后续统一管理和更新:

class FormStepData {
  final String step1Name;
  final int step2Age;
  final String step3Address;

  FormStepData({
    this.step1Name = '',
    this.step2Age = 0,
    this.step3Address = '',
  });

  // 复制方法,用于局部更新数据
  FormStepData copyWith({
    String? step1Name,
    int? step2Age,
    String? step3Address,
  }) {
    return FormStepData(
      step1Name: step1Name ?? this.step1Name,
      step2Age: step2Age ?? this.step2Age,
      step3Address: step3Address ?? this.step3Address,
    );
  }
}

2. 父组件(表单容器)

负责状态管理、进度计算、页面切换和数据收集:

import 'package:flutter/material.dart';

class MultiPageForm extends StatefulWidget {
  const MultiPageForm({super.key});

  @override
  State<MultiPageForm> createState() => _MultiPageFormState();
}

class _MultiPageFormState extends State<MultiPageForm> {
  final int _totalSteps = 3;
  int _currentStep = 0;
  late FormStepData _formData;

  @override
  void initState() {
    super.initState();
    _formData = FormStepData();
  }

  // 保存当前步骤的数据到全局表单对象
  void _saveStepData(dynamic data) {
    setState(() {
      switch (_currentStep) {
        case 0:
          _formData = _formData.copyWith(step1Name: data as String);
          break;
        case 1:
          _formData = _formData.copyWith(step2Age: data as int);
          break;
        case 2:
          _formData = _formData.copyWith(step3Address: data as String);
          break;
      }
    });
  }

  // 跳转到指定步骤(支持编辑)
  void _goToStep(int stepIndex) {
    setState(() {
      _currentStep = stepIndex;
    });
  }

  // 提交整个表单,处理最终数据
  void _submitForm() {
    // 这里可以替换为API提交、本地存储等逻辑
    print('最终表单数据:姓名=${_formData.step1Name},年龄=${_formData.step2Age},地址=${_formData.step3Address}');
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('表单提交成功!')),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('多页面表单')),
      body: Column(
        children: [
          // 顶部线性进度条
          LinearProgressIndicator(
            value: (_currentStep + 1) / _totalSteps,
            minHeight: 8,
            backgroundColor: Colors.grey[300],
            color: Colors.blue,
          ),
          // 可点击的步骤指示器(支持直接编辑任意步骤)
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(_totalSteps, (index) {
                return GestureDetector(
                  onTap: () => _goToStep(index),
                  child: Container(
                    width: 16,
                    height: 16,
                    margin: const EdgeInsets.symmetric(horizontal: 8),
                    decoration: BoxDecoration(
                      color: index == _currentStep ? Colors.blue : Colors.grey[300],
                      shape: BoxShape.circle,
                    ),
                    child: index < _currentStep
                        ? const Icon(Icons.check, size: 12, color: Colors.white)
                        : null,
                  ),
                );
              }),
            ),
          ),
          // 当前步骤的页面内容
          Expanded(
            child: _buildCurrentStepPage(),
          ),
          // 底部导航按钮
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                if (_currentStep > 0)
                  ElevatedButton(
                    onPressed: () => _goToStep(_currentStep - 1),
                    child: const Text('上一步'),
                  ),
                if (_currentStep < _totalSteps - 1)
                  ElevatedButton(
                    onPressed: () => _goToStep(_currentStep + 1),
                    child: const Text('下一步'),
                  ),
                if (_currentStep == _totalSteps - 1)
                  ElevatedButton(
                    onPressed: _submitForm,
                    style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
                    child: const Text('提交'),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // 根据当前步骤加载对应页面
  Widget _buildCurrentStepPage() {
    switch (_currentStep) {
      case 0:
        return Step1Page(
          initialData: _formData.step1Name,
          onSave: _saveStepData,
        );
      case 1:
        return Step2Page(
          initialData: _formData.step2Age,
          onSave: _saveStepData,
        );
      case 2:
        return Step3Page(
          initialData: _formData.step3Address,
          onSave: _saveStepData,
        );
      default:
        return const SizedBox();
    }
  }
}

3. 独立步骤页面示例(以第一步为例)

每个步骤是独立的组件,接收初始数据和保存回调:

class Step1Page extends StatefulWidget {
  final String initialData;
  final Function(dynamic) onSave;

  const Step1Page({
    super.key,
    required this.initialData,
    required this.onSave,
  });

  @override
  State<Step1Page> createState() => _Step1PageState();
}

class _Step1PageState extends State<Step1Page> {
  late TextEditingController _nameController;

  @override
  void initState() {
    super.initState();
    _nameController = TextEditingController(text: widget.initialData);
    // 实时监听输入变化,自动保存到全局数据
    _nameController.addListener(() {
      widget.onSave(_nameController.text);
    });
  }

  @override
  void dispose() {
    _nameController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('请输入姓名', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          const SizedBox(height: 16),
          TextField(
            controller: _nameController,
            decoration: const InputDecoration(
              hintText: '请填写您的姓名',
              border: OutlineInputBorder(),
            ),
          ),
        ],
      ),
    );
  }
}

其他步骤页面(如年龄、地址)可以参照上述结构实现,只需修改数据类型和UI即可。

关键要点说明

  • 状态统一管理:父组件持有全表单数据,避免各步骤数据分散,确保编辑时能加载已填写内容。
  • 进度计算逻辑:进度条的value通过(当前步骤索引 + 1) / 总步骤数计算,直观展示已完成的进度。
  • 编辑支持:点击步骤导航点可直接跳转到对应页面,父组件切换currentStep即可加载目标页面,数据自动同步。
  • 数据保存:步骤页面通过监听输入变化实时保存数据,也可以改为在跳转前手动触发保存(比如在_goToStep方法中调用当前页面的保存逻辑)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 05:50:57