API分类数据转多级列表 实现联动DropdownButton技术求助
Hey there! Let's build that 4-level cascading DropdownButton you're looking for—exactly like the reference you shared. Since you already have the model generated via quicktype, we can jump straight into the implementation. I'll use Flutter for this example, since DropdownButton is a core Flutter widget.
Step 1: Confirm Your Model Structure
First, let's make sure your generated model matches the hierarchy you described. It should look something like this (adjust class names if your quicktype output differs):
class Category { final String name; final List<ChildCategory>? children; Category({required this.name, this.children}); // Your quicktype-generated fromJson/toJson methods will live here } class ChildCategory { final String name; final List<ChildLvCategory>? childrenLv; ChildCategory({required this.name, this.childrenLv}); } class ChildLvCategory { final String name; final List<FinalChildCategory>? children; ChildLvCategory({required this.name, this.children}); } class FinalChildCategory { final String name; FinalChildCategory({required this.name}); }
Step 2: Track State for Selections & Options
We'll need state variables to keep track of the selected category at each level, plus the list of available options for each dropdown:
// Selected categories at each level Category? _selectedLevel1; ChildCategory? _selectedLevel2; ChildLvCategory? _selectedLevel3; FinalChildCategory? _selectedLevel4; // Available options for each dropdown List<Category> _level1Options = []; List<ChildCategory> _level2Options = []; List<ChildLvCategory> _level3Options = []; List<FinalChildCategory> _level4Options = [];
In your widget's initState, load your parsed API data into the first level's options:
@override void initState() { super.initState(); // Replace this with your actual API fetch & parsing logic _level1Options = fetchAndParseCategoriesFromApi(); }
Step 3: Implement Cascading Logic
The magic happens in the onChanged handlers—we'll update lower-level options and reset their selected values whenever a higher-level option is chosen.
Level 1 Dropdown
DropdownButton<Category>( value: _selectedLevel1, hint: const Text('Select Level 1 Category'), items: _level1Options.map((cat) { return DropdownMenuItem( value: cat, child: Text(cat.name), ); }).toList(), onChanged: (Category? newVal) { setState(() { _selectedLevel1 = newVal; // Update Level 2 options based on the selected Level 1 category _level2Options = newVal?.children ?? []; // Reset all lower levels to avoid invalid selections _selectedLevel2 = null; _level3Options = []; _selectedLevel3 = null; _level4Options = []; _selectedLevel4 = null; }); }, isExpanded: true, padding: const EdgeInsets.symmetric(horizontal: 12), )
Level 2 Dropdown (Disabled until Level 1 is selected)
DropdownButton<ChildCategory>( value: _selectedLevel2, hint: const Text('Select Level 2 Category'), items: _level2Options.map((cat) { return DropdownMenuItem( value: cat, child: Text(cat.name), ); }).toList(), onChanged: _selectedLevel1 == null ? null // Disable if no Level 1 category is selected : (ChildCategory? newVal) { setState(() { _selectedLevel2 = newVal; _level3Options = newVal?.childrenLv ?? []; _selectedLevel3 = null; _level4Options = []; _selectedLevel4 = null; }); }, isExpanded: true, disabledHint: const Text('Please select Level 1 first'), padding: const EdgeInsets.symmetric(horizontal: 12), )
Level 3 Dropdown (Disabled until Level 2 is selected)
DropdownButton<ChildLvCategory>( value: _selectedLevel3, hint: const Text('Select Level 3 Category'), items: _level3Options.map((cat) { return DropdownMenuItem( value: cat, child: Text(cat.name), ); }).toList(), onChanged: _selectedLevel2 == null ? null : (ChildLvCategory? newVal) { setState(() { _selectedLevel3 = newVal; _level4Options = newVal?.children ?? []; _selectedLevel4 = null; }); }, isExpanded: true, disabledHint: const Text('Please select Level 2 first'), padding: const EdgeInsets.symmetric(horizontal: 12), )
Level 4 Dropdown (Disabled until Level 3 is selected)
DropdownButton<FinalChildCategory>( value: _selectedLevel4, hint: const Text('Select Level 4 Category'), items: _level4Options.map((cat) { return DropdownMenuItem( value: cat, child: Text(cat.name), ); }).toList(), onChanged: _selectedLevel3 == null ? null : (FinalChildCategory? newVal) { setState(() { _selectedLevel4 = newVal; // Add logic here to use the final selected category! }); }, isExpanded: true, disabledHint: const Text('Please select Level 3 first'), padding: const EdgeInsets.symmetric(horizontal: 12), )
Step 4: Assemble the Full Widget
Wrap all dropdowns in a Column with spacing for a clean, user-friendly layout:
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('4-Level Category Selector')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DropdownButton<Category>(...), const SizedBox(height: 16), DropdownButton<ChildCategory>(...), const SizedBox(height: 16), DropdownButton<ChildLvCategory>(...), const SizedBox(height: 16), DropdownButton<FinalChildCategory>(...), ], ), ), ); }
Pro Tips for Polish
- State Management: For complex apps, use
ProviderorRiverpodto share selected category state across screens instead of local widget state. - Error Handling: Add loading indicators and error messages for when API fetching fails.
- Styling: Customize dropdowns with
Decorationto match your app's design (e.g., rounded corners, borders). - Empty States: If a level has no subcategories, show a message like "No subcategories available" instead of an empty dropdown.
内容的提问来源于stack exchange,提问作者Mohammed El-Barbeer

