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

Flutter:如何在前一个DropDownFormField选中数据后创建关联的DropDownFormField

Cascading DropDownFormField Example in Flutter

Hey there! I’ve built exactly this kind of cascading dropdown functionality before—here’s a straightforward, working example that you can adapt for your use case.

Core Idea

We’ll use a StatefulWidget to track two pieces of state:

  • The selected main option (House/Hotel/Apartment)
  • The selected sub-option (depends on the main choice)
    We’ll also define a map that links each main option to its corresponding sub-options.

Complete Code

import 'package:flutter/material.dart';

class CascadingDropdowns extends StatefulWidget {
  const CascadingDropdowns({super.key});

  @override
  State<CascadingDropdowns> createState() => _CascadingDropdownsState();
}

class _CascadingDropdownsState extends State<CascadingDropdowns> {
  // Track selected values
  String? _selectedMainOption;
  String? _selectedSubOption;

  // Map main options to their sub-options
  final Map<String, List<String>> _subOptionMap = {
    'House': ['Room', 'Kitchen'],
    'Hotel': ['Single Room', 'Double Room'],
    'Apartment': ['Bedroom', 'Living Room', 'Balcony'], // Added for completeness
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cascading Dropdowns')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // First DropdownFormField
            DropdownFormField<String>(
              decoration: const InputDecoration(
                labelText: 'Select Property Type',
                border: OutlineInputBorder(),
              ),
              value: _selectedMainOption,
              items: _subOptionMap.keys.map((option) {
                return DropdownMenuItem(
                  value: option,
                  child: Text(option),
                );
              }).toList(),
              onChanged: (newValue) {
                setState(() {
                  _selectedMainOption = newValue;
                  // Reset sub-option when main option changes
                  _selectedSubOption = null;
                });
              },
              validator: (value) {
                if (value == null) {
                  return 'Please select a property type';
                }
                return null;
              },
            ),
            const SizedBox(height: 20),
            // Second DropdownFormField (only shows if main option is selected)
            if (_selectedMainOption != null)
              DropdownFormField<String>(
                decoration: InputDecoration(
                  labelText: 'Select ${_selectedMainOption} Area',
                  border: const OutlineInputBorder(),
                ),
                value: _selectedSubOption,
                items: _subOptionMap[_selectedMainOption]!.map((subOption) {
                  return DropdownMenuItem(
                    value: subOption,
                    child: Text(subOption),
                  );
                }).toList(),
                onChanged: (newValue) {
                  setState(() {
                    _selectedSubOption = newValue;
                  });
                },
                validator: (value) {
                  if (value == null) {
                    return 'Please select an area';
                  }
                  return null;
                },
              ),
            const SizedBox(height: 20),
            // Optional: Button to show selected values
            ElevatedButton(
              onPressed: () {
                if (Form.of(context)?.validate() ?? false) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(
                        'Selected: $_selectedMainOption > $_selectedSubOption',
                      ),
                    ),
                  );
                }
              },
              child: const Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }
}

// Helper widget (convenience wrapper for Dropdown + FormField)
class DropdownFormField<T> extends FormField<T> {
  DropdownFormField({
    super.key,
    super.validator,
    required List<DropdownMenuItem<T>> items,
    T? value,
    required void Function(T?) onChanged,
    required InputDecoration decoration,
  }) : super(
          initialValue: value,
          builder: (FormFieldState<T> state) {
            return InputDecorator(
              decoration: decoration.copyWith(
                errorText: state.errorText,
              ),
              child: DropdownButtonHideUnderline(
                child: DropdownButton<T>(
                  value: state.value,
                  isExpanded: true,
                  items: items,
                  onChanged: (newValue) {
                    state.didChange(newValue);
                    onChanged(newValue);
                  },
                ),
              ),
            );
          },
        );
}

// To run this, add it to your MaterialApp home:
// home: const CascadingDropdowns(),

Key Details Explained

  • State Management: We use setState to update the UI whenever selected options change—this ensures the second dropdown updates immediately when the first choice is modified.
  • Resetting Sub-Option: When the main option changes, we reset _selectedSubOption to null to avoid showing an invalid sub-option that doesn’t match the new main choice.
  • Conditional Rendering: The second dropdown only appears once a main option is selected (using the if statement in the widget tree).
  • Form Validation: Both dropdowns include validation to ensure the user selects an option before submitting—you can remove this if it’s not needed for your use case.

Bonus: Dynamic Sub-Options (from API)

If your sub-options come from an API instead of a static map, modify the onChanged of the first dropdown to fetch data:

onChanged: (newValue) async {
  setState(() {
    _selectedMainOption = newValue;
    _selectedSubOption = null;
  });
  // Fetch sub-options from API
  final subOptions = await _fetchSubOptions(newValue!);
  setState(() {
    _subOptionMap[newValue] = subOptions;
  });
},

This example should cover your exact requirements—feel free to tweak labels, sub-options, or styling to match your app’s design!

内容的提问来源于stack exchange,提问作者Sin Lee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 14:07:30