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

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()),
          ],
        ),
      ),
    );
  }
}

关键实现说明

  1. 分层状态管理:每个层级的下拉菜单维护自己的选中状态(_selectedKey、_selectedValue、_subSelection),避免状态混乱
  2. 递归渲染:当选中值为Map时,递归调用DynamicNestedDropdown组件,支持任意深度的嵌套结构
  3. 选中值回调:通过onSelectionChanged回调向上传递选中结果,最终组装成完整的JSON结构
  4. 类型判断:根据选中值的类型(List/Map)渲染对应的下拉菜单,List类型直接展示选项,Map类型进入下一级

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 12:18:09