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

Flutter中如何连续展示数量未知的Dialog表单并管理状态?

解决方案

你的核心问题在于每个Dialog里的FormQuestions都是独立实例,状态没有共享,也没有统一存储所有问答数据。要实现多表单的前进后退和数据保存,需要把数据管理抽离到父组件,让每个表单实例共享同一套数据,并通过索引控制当前显示的表单。

步骤1:定义数据模型

先创建一个类来存储单组问答:

class QuestionAnswer {
  String question;
  String answer;

  QuestionAnswer({this.question = '', this.answer = ''});
}

步骤2:父组件管理全局状态

在调用Dialog的页面(比如主页面)维护所有问答数据、当前索引,以及Dialog的显示状态:

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

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  final List<QuestionAnswer> _qaList = [QuestionAnswer()]; // 初始一组空表单
  int _currentIndex = 0;
  bool _isDialogOpen = false;

  // 打开表单Dialog
  void _openFormDialog() {
    setState(() => _isDialogOpen = true);
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => StatefulBuilder(
        builder: (context, setStateDialog) => Dialog(
          child: FormQuestions(
            currentQA: _qaList[_currentIndex],
            onSave: (qa) {
              _qaList[_currentIndex] = qa;
            },
            onNext: () {
              // 验证当前表单后,处理Next逻辑
              if (_currentIndex == _qaList.length - 1) {
                // 最后一组,添加新的空表单
                _qaList.add(QuestionAnswer());
              }
              setStateDialog(() => _currentIndex++);
            },
            onBack: () {
              // 验证当前表单后,回到上一组
              if (_currentIndex > 0) {
                setStateDialog(() => _currentIndex--);
              }
            },
            onSubmit: () {
              // 提交所有数据
              Navigator.of(context).pop();
              setState(() {
                _isDialogOpen = false;
                _currentIndex = 0;
              });
              // 这里写你的提交逻辑,比如打印所有数据
              for (var qa in _qaList) {
                print('问题:${qa.question},答案:${qa.answer}');
              }
            },
            canGoBack: _currentIndex > 0,
          ),
        ),
      ),
    ).then((_) {
      // 关闭Dialog后重置索引
      setState(() => _currentIndex = 0);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('问答表单')),
      body: Center(
        child: ElevatedButton(
          onPressed: _openFormDialog,
          child: const Text('开始添加问答'),
        ),
      ),
    );
  }
}

步骤3:修改FormQuestions组件

让它接收父组件传递的数据和回调,不再维护独立状态:

class FormQuestions extends StatefulWidget {
  final QuestionAnswer currentQA;
  final Function(QuestionAnswer) onSave;
  final VoidCallback onNext;
  final VoidCallback onBack;
  final VoidCallback onSubmit;
  final bool canGoBack;

  const FormQuestions({
    super.key,
    required this.currentQA,
    required this.onSave,
    required this.onNext,
    required this.onBack,
    required this.onSubmit,
    required this.canGoBack,
  });

  @override
  State<FormQuestions> createState() => _FormQuestionsState();
}

class _FormQuestionsState extends State<FormQuestions> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  late TextEditingController _questionController;
  late TextEditingController _answerController;

  @override
  void initState() {
    super.initState();
    // 初始化控制器,加载当前组的已有数据
    _questionController = TextEditingController(text: widget.currentQA.question);
    _answerController = TextEditingController(text: widget.currentQA.answer);
  }

  @override
  void didUpdateWidget(covariant FormQuestions oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 切换表单时更新控制器内容
    if (oldWidget.currentQA != widget.currentQA) {
      _questionController.text = widget.currentQA.question;
      _answerController.text = widget.currentQA.answer;
    }
  }

  @override
  void dispose() {
    _questionController.dispose();
    _answerController.dispose();
    super.dispose();
  }

  // 验证并保存当前表单数据
  bool _saveCurrentForm() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      widget.onSave(QuestionAnswer(
        question: _questionController.text,
        answer: _answerController.text,
      ));
      return true;
    }
    return false;
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: _questionController,
              decoration: const InputDecoration(
                labelText: 'Question',
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value!.isEmpty) return '请输入问题标题';
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _answerController,
              minLines: 3,
              maxLines: 5,
              decoration: const InputDecoration(
                labelText: 'Answer',
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value!.isEmpty || value.length < 30) {
                  return '回答长度不能少于30字符';
                }
                return null;
              },
            ),
            const SizedBox(height: 20),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: widget.canGoBack
                        ? () {
                            if (_saveCurrentForm()) {
                              widget.onBack();
                            }
                          }
                        : null,
                    child: const Text('Back'),
                  ),
                ),
                const SizedBox(width: 10),
                Expanded(
                  flex: 2,
                  child: ElevatedButton(
                    onPressed: () {
                      if (_saveCurrentForm()) {
                        widget.onSubmit();
                      }
                    },
                    child: const Text('Submit'),
                  ),
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      if (_saveCurrentForm()) {
                        widget.onNext();
                      }
                    },
                    child: const Text('Next'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

关键修改说明

  • 数据共享:所有问答数据存在父组件的_qaList中,每个表单操作的都是列表里的对应元素,确保前进后退时能加载之前输入的内容。
  • 控制器同步:用TextEditingController绑定输入框,切换表单时更新控制器文本,实现数据回显。
  • 表单验证:点击Next/Back/Submit时先验证当前表单,通过后才执行对应操作,保证数据合法性。
  • 状态回调:父组件通过回调函数控制表单切换和提交逻辑,子组件只负责渲染和验证当前表单。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 00:15:04