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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 05:21:23