Flutter实现依赖前置选项的动态多级下拉列表方案
Flutter 递归嵌套层级问答下拉实现方案
一、递归数据模型设计
你的接口返回的JSON本身就是同结构递归嵌套格式,不需要为不同层级单独定义模型,单个递归类即可覆盖所有层级的解析,同时兼容你示例中存在的variant/variants字段名笔误问题。
/// 统一的问题/选项节点模型,递归支持任意层级嵌套 class QuestionNode { final String? name; final String? id; final List<QuestionNode>? variants; // 当前问题下的可选项列表 final QuestionNode? additions; // 选中当前选项后关联的下一级子问题,为空则无下级 QuestionNode({ this.name, this.id, this.variants, this.additions, }); factory QuestionNode.fromJson(Map<String, dynamic> json) { // 兼容接口里variant/variants两种key写法 final rawVariantList = json['variant'] ?? json['variants']; // 判定子问题是否有效:name和id全为空则视为无下级问题 final validAddition = json['additions'] != null && (json['additions']['name'] != null || json['additions']['id'] != null); return QuestionNode( name: json['name'], id: json['id'], variants: rawVariantList?.map((item) => QuestionNode.fromJson(item)).toList(), additions: validAddition ? QuestionNode.fromJson(json['additions']) : null, ); } }
解析时直接将接口返回最外层的additions字段传入QuestionNode.fromJson(),即可拿到完整的根问题节点,不需要做额外的层级转换。判断是否需要展示下一级问题,只需要检查当前选中选项的additions字段是否为空,没有任何硬编码的层级判断逻辑。
二、逐步渲染下拉组件的实现
用基础的StatefulWidget就能实现效果,不需要引入复杂的状态管理框架,核心思路是用一个列表维护当前需要展示的问题链路,根据用户选择动态增减列表内容:
- 初始化阶段:接口请求完成、数据解析成功后,只把根问题节点放入展示列表,页面首次只渲染第一个下拉框
- 选择交互:用户操作某一级下拉框选中选项时,先清除当前层级之后的所有旧问题(避免用户修改上级选择后,下级还残留不匹配的旧选项),再判断当前选中选项是否存在子问题,存在则将子问题追加到展示列表,触发UI刷新后新的下拉框会自动渲染
- 结果收集:提交时直接遍历展示列表,收集每一级的选中值就是完整的选择链路,可直接传给后端接口
核心实现代码如下:
/// 维护每一级展示的问题,以及用户在该问题下的选中值 class QuestionWithSelection { final QuestionNode question; QuestionNode? selectedValue; QuestionWithSelection({required this.question, this.selectedValue}); } class NestedQuestionPage extends StatefulWidget { const NestedQuestionPage({super.key}); @override State<NestedQuestionPage> createState() => _NestedQuestionPageState(); } class _NestedQuestionPageState extends State<NestedQuestionPage> { QuestionNode? _rootQuestion; final List<QuestionWithSelection> _displayList = []; @override void initState() { super.initState(); _fetchQuestionData(); } /// 接口请求与数据初始化 Future<void> _fetchQuestionData() async { // 替换为你的真实接口请求逻辑 final apiResponse = await yourRequestMethod.getQuestionConfig(); final root = QuestionNode.fromJson(apiResponse['additions']); setState(() { _rootQuestion = root; // 初始化仅添加第一个问题 _displayList.add(QuestionWithSelection(question: root)); }); } /// 下拉选择回调 void _handleSelect(int currentLevel, QuestionNode selectedOption) { setState(() { // 更新当前级选中值 _displayList[currentLevel].selectedValue = selectedOption; // 移除当前级之后的所有无效下级问题 _displayList.removeRange(currentLevel + 1, _displayList.length); // 存在子问题则追加到展示列表 if (selectedOption.additions != null) { _displayList.add(QuestionWithSelection(question: selectedOption.additions!)); } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('信息选择')), body: ListView.builder( padding: const EdgeInsets.all(16), itemCount: _displayList.length, itemBuilder: (context, index) { final currentItem = _displayList[index]; return Container( margin: const EdgeInsets.only(bottom: 24), // 可替换为项目中使用的任意下拉选择组件 child: DropdownButtonFormField<QuestionNode>( decoration: InputDecoration( labelText: currentItem.question.name, border: const OutlineInputBorder(), ), value: currentItem.selectedValue, items: currentItem.question.variants?.map((option) { return DropdownMenuItem( value: option, child: Text(option.name ?? ''), ); }).toList(), onChanged: (val) { if (val != null) _handleSelect(index, val); }, ), ); }, ), ); } }
补充说明
- 该实现没有层级数量限制,哪怕嵌套几十层也可以正常运行
- 如果需要做默认选中回填,只需要在初始化时根据默认选中链路,逐级把对应问题和选中值加入
_displayList即可 - 最后提交时,遍历
_displayList收集每一项的selectedValue的id、name字段,就能拿到完整的用户选择结果
内容的提问来源于stack exchange,提问作者Andrii Havrylyak
相关产品推荐
相关产品推荐

