Flutter如何基于GET API动态生成多类型输入表单并提交数据
Flutter 动态表单实现方案
实现逻辑完全基于Flutter原生组件,无需引入第三方依赖,后续扩展控件类型、校验规则都非常方便。
1. 定义数据实体类
先把接口返回的JSON结构转换为强类型Dart实体,避免后续取值出现类型错误。注意示例接口中控件类型的键名存在拼写错误(写为cotrollType),代码里做了双向兼容,后端修正键名后也能正常运行。
class QuestionItem { final String question; // 题目标题/输入字段提示 final String controlType; // 控件类型 final List<String> values; // 选项集合 final List<String> validations; // 校验规则列表 QuestionItem({ required this.question, required this.controlType, required this.values, required this.validations, }); factory QuestionItem.fromJson(Map<String, dynamic> json) { return QuestionItem( question: json['question'] as String, // 兼容接口拼写错误的键名 controlType: (json['cotrollType'] ?? json['controlType']) as String, values: List<String>.from(json['values'] ?? []), validations: (json['validations'] as List? ?? []) .map((e) => e['validation'] as String) .toList(), ); } }
2. 核心逻辑说明
用Flutter自带的Form组件做全局表单状态管理,通过控件类型字段动态匹配要渲染的输入组件,同时把接口返回的校验规则统一接入表单校验流程:
- 控件类型为
textField时,渲染文本输入框 - 控件类型为
dropdown时,渲染下拉选择框 - 后续需要支持checkbox、图片上传、文件上传等控件时,只需要新增类型判断分支即可
- 校验逻辑统一收口,新增长度校验、格式校验等规则只需要加对应判断分支,不需要修改每个控件的单独代码
3. 完整页面实现代码
import 'package:flutter/material.dart'; class DynamicFormPage extends StatefulWidget { const DynamicFormPage({super.key, required this.questionList}); // 接口拉取后转好的问题实体列表 final List<QuestionItem> questionList; @override State<DynamicFormPage> createState() => _DynamicFormPageState(); } class _DynamicFormPageState extends State<DynamicFormPage> { final _formKey = GlobalKey<FormState>(); // 存储所有题目的用户答案,key为题目索引 final Map<int, dynamic> _formValue = {}; // 统一校验方法 String? _validateInput(dynamic value, List<String> rules) { for (final rule in rules) { switch (rule) { case 'IsRequired': if (value == null || (value is String && value.trim().isEmpty) || (value is List && value.isEmpty)) { return '该字段为必填项'; } break; // 新增校验规则直接在这里加case即可 } } return null; } // 根据控件类型渲染对应输入组件 Widget _buildFormControl(int index, QuestionItem item) { switch (item.controlType) { case 'textField': return TextFormField( decoration: InputDecoration( labelText: item.question, border: const OutlineInputBorder(), ), validator: (value) => _validateInput(value, item.validations), onSaved: (value) => _formValue[index] = value?.trim(), ); case 'dropdown': return DropdownButtonFormField<String>( decoration: InputDecoration( labelText: item.question, border: const OutlineInputBorder(), ), items: item.values .map((option) => DropdownMenuItem( value: option, child: Text(option), )) .toList(), validator: (value) => _validateInput(value, item.validations), onChanged: (value) => _formValue[index] = value, onSaved: (value) => _formValue[index] = value, ); // 扩展其他控件直接在这里加case // case 'checkbox': // case 'image': // case 'file': default: return Text('暂不支持的控件类型: ${item.controlType}'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('动态测验')), body: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: ListView.separated( itemCount: widget.questionList.length, separatorBuilder: (_, __) => const SizedBox(height: 20), itemBuilder: (context, index) { final question = widget.questionList[index]; return _buildFormControl(index, question); }, ), ), ), bottomNavigationBar: Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: () { // 提交时先触发全量校验 if (_formKey.currentState?.validate() ?? false) { _formKey.currentState?.save(); // _formValue就是所有题目的答案,直接传给提交接口即可 debugPrint('表单提交数据: $_formValue'); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('提交成功')), ); } }, child: const Text('提交答案'), ), ), ); } }
4. 使用方式
接口请求拿到返回的JSON数组后,遍历数组通过QuestionItem.fromJson方法转成实体列表,传入DynamicFormPage即可自动渲染出对应表单。
内容的提问来源于stack exchange,提问作者Emmanuel Agbo
相关产品推荐
相关产品推荐

