Flutter动态嵌套下拉组件实现及选中值JSON返回方案问询
动态多级下拉菜单Flutter组件实现
需求说明
需要实现一个Flutter Widget,根据API返回的嵌套JSON数据动态渲染多级下拉菜单,并返回选中值的JSON结构。API返回的数据示例如下:
{ "value1": ["option1", "option2"], "value2": { "subvalue1": ["option1", "option2"], "subvalue2": ["option3", "option4"] }, "value3": { "subvalue1": { "anothersubvalue1": ["option1", "option2"], "anothersubvalue2": ["option1", "option2"] }, "subvalue2": { "anothersubvalue1": ["option4", "option5"], "anothersubvalue2": ["option5", "option6"] } } }
具体需求:
- 首次渲染顶级下拉菜单,展示
value1、value2、value3等选项 - 选中项对应值为列表时,渲染以该选中项为标题、列表内容为选项的下拉菜单
- 选中项对应值为Map时,渲染下一级下拉菜单,展示Map的键作为选项,支持任意层级嵌套
- 组件需返回包含各层级选中值的JSON数据
原代码问题
用户尝试的代码无法正常工作,原代码如下:
class DynamicDropdowns extends StatefulWidget { final Map<String, dynamic> data; DynamicDropdowns({required this.data}); @override _DynamicDropdownsState createState() => _DynamicDropdownsState(); } class _DynamicDropdownsState extends State<DynamicDropdowns> { Map<String, dynamic> selectedValues = {}; @override Widget build(BuildContext context) { return Column( children: [ DropdownButton<String>( value: selectedValues.keys.isEmpty ? null : selectedValues.keys.last, items: widget.data.keys.map((value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), onChanged: (newValue) { setState(() { selectedValues.removeWhere( (key, _) => widget.data[key] is Map || key == newValue); selectedValues[newValue] = null; }); }, ), if (selectedValues.isNotEmpty) DynamicDropdowns( data: widget.data[selectedValues.keys.last], ), ], ); } Map<String, dynamic> getSelectedValues() { Map<String, dynamic> result = {}; for (var key in selectedValues.keys) { if (widget.data[key] is Map) { final subResult = DynamicDropdowns( data: widget.data[key], ).getSelectedValues(); if (subResult.isNotEmpty) { result[key] = subResult; } } else { result[key] = selectedValues[key]; } } return result; } }
解决方案
以下是修正后的通用组件实现,支持任意层级嵌套,并能正确收集选中值:
import 'package:flutter/material.dart'; class DynamicNestedDropdown extends StatefulWidget { final Map<String, dynamic> data; final ValueChanged<Map<String, dynamic>> onSelectionChanged; final String? parentKey; const DynamicNestedDropdown({ super.key, required this.data, required this.onSelectionChanged, this.parentKey, }); @override State<DynamicNestedDropdown> createState() => _DynamicNestedDropdownState(); } class _DynamicNestedDropdownState extends State<DynamicNestedDropdown> { String? _selectedKey; dynamic _selectedValue; Map<String, dynamic> _subSelection = {}; @override void initState() { super.initState(); _updateSelection(); } void _updateSelection() { Map<String, dynamic> currentSelection = {}; if (_selectedKey != null) { if (_selectedValue is List) { currentSelection[_selectedKey!] = _selectedValue; } else if (_selectedValue is Map && _subSelection.isNotEmpty) { currentSelection[_selectedKey!] = _subSelection; } } if (widget.parentKey != null) { widget.onSelectionChanged({widget.parentKey!: currentSelection}); } else { widget.onSelectionChanged(currentSelection); } } void _handleKeySelection(String newKey) { setState(() { _selectedKey = newKey; _selectedValue = widget.data[newKey]; _subSelection = {}; }); _updateSelection(); } void _handleValueSelection(dynamic value) { setState(() { _selectedValue = value; }); _updateSelection(); } void _handleSubSelection(Map<String, dynamic> subSelection) { setState(() { _subSelection = subSelection; }); _updateSelection(); } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 渲染当前层级的下拉菜单(键选择) DropdownButton<String>( hint: const Text('请选择'), value: _selectedKey, items: widget.data.keys.map((key) { return DropdownMenuItem( value: key, child: Text(key), ); }).toList(), onChanged: (newKey) { if (newKey != null) { _handleKeySelection(newKey); } }, ), const SizedBox(height: 16), // 根据选中值的类型渲染下一级 if (_selectedValue != null) if (_selectedValue is List) // 列表类型:渲染选项下拉菜单 DropdownButton<dynamic>( hint: Text('选择${_selectedKey}的选项'), value: _selectedValue is List ? _selectedValue : null, items: (_selectedValue as List).map((option) { return DropdownMenuItem( value: option, child: Text(option.toString()), ); }).toList(), onChanged: (value) { if (value != null) { _handleValueSelection(value); } }, ) else if (_selectedValue is Map) // Map类型:递归渲染子下拉菜单 DynamicNestedDropdown( data: _selectedValue as Map<String, dynamic>, onSelectionChanged: _handleSubSelection, parentKey: _selectedKey, ), const SizedBox(height: 16), ], ); } } // 使用示例 class DropdownDemo extends StatefulWidget { const DropdownDemo({super.key}); @override State<DropdownDemo> createState() => _DropdownDemoState(); } class _DropdownDemoState extends State<DropdownDemo> { Map<String, dynamic> _selectedResult = {}; final Map<String, dynamic> apiData = { "value1": ["option1", "option2"], "value2": { "subvalue1": ["option1", "option2"], "subvalue2": ["option3", "option4"] }, "value3": { "subvalue1": { "anothersubvalue1": ["option1", "option2"], "anothersubvalue2": ["option1", "option2"] }, "subvalue2": { "anothersubvalue1": ["option4", "option5"], "anothersubvalue2": ["option5", "option6"] } } }; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('动态多级下拉菜单')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ DynamicNestedDropdown( data: apiData, onSelectionChanged: (result) { setState(() { _selectedResult = result; }); }, ), const SizedBox(height: 24), const Text('选中结果:'), const SizedBox(height: 8), Text(_selectedResult.toString()), ], ), ), ); } }
关键实现说明
- 分层状态管理:每个层级的下拉菜单维护自己的选中状态(
_selectedKey、_selectedValue、_subSelection),避免状态混乱 - 递归渲染:当选中值为Map时,递归调用
DynamicNestedDropdown组件,支持任意深度的嵌套结构 - 选中值回调:通过
onSelectionChanged回调向上传递选中结果,最终组装成完整的JSON结构 - 类型判断:根据选中值的类型(List/Map)渲染对应的下拉菜单,List类型直接展示选项,Map类型进入下一级
内容的提问来源于stack exchange,提问作者nuked nuts
相关产品推荐
相关产品推荐

