Flutter DropdownButton2自定义下拉组件选中「All」后显示文本与值不一致问题
你好,我帮你排查了这个CustomDropdown组件的问题,选中「All」后显示文本与值不一致的核心原因是几个逻辑细节的漏洞,下面是具体分析和修复方案:
问题根源分析
- 空列表的Fallback逻辑错误:当
widget.items为空且showAllOption为true时,selectedItemBuilder中firstWhere的orElse: () => widget.items.first会直接抛出StateError,因为空列表调用first方法是非法的,这会导致组件崩溃或显示异常。 - selectedValue匹配不严谨:原代码中
selectedValue直接使用widget.value ?? (widget.showAllOption ? "all" : null),如果widget.value是一个不存在于dropdownItems中的无效值,会导致选中状态匹配失败,显示异常。 - ScrollbarThemeData语法不完整:原代码中
thumbColor: WidgetStateProperty.all(Colors.未完成,属于语法错误,会影响下拉菜单的正常渲染。
修复后的完整代码
import 'package:flutter/material.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:sufrah_adminpanel/core/styles/app_color.dart'; import 'package:sufrah_adminpanel/shared/presentations/widgets/text/text_style.dart'; import 'package:sufrah_adminpanel/shared/presentations/widgets/text/text_widget.dart'; class CustomDropdown<T> extends StatefulWidget { final String hintText; final List<T> items; final String Function(T) getLabel; // how to display the item final String Function(T) getValue; // value id/key final ValueChanged<String?> onChanged; final String? value; final double? width; final double? menuMaxHeight; final Color dropdownColor; final Color iconColor; final bool showAllOption; final bool isAlternativeDesign; const CustomDropdown({ super.key, required this.hintText, required this.items, required this.getLabel, required this.getValue, required this.onChanged, this.value, this.width, this.menuMaxHeight, this.dropdownColor = Colors.black, this.iconColor = Colors.white, this.showAllOption = true, this.isAlternativeDesign = false, }); @override State<CustomDropdown<T>> createState() => _CustomDropdownState<T>(); } class _CustomDropdownState<T> extends State<CustomDropdown<T>> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 200), ); } @override void dispose() { _controller.dispose(); super.dispose(); } void _onMenuStateChange(bool isOpen) { if (isOpen) { _controller.forward(); } else { _controller.reverse(); } } @override Widget build(BuildContext context) { // Build DropdownMenuItems dynamically final dropdownItems = <DropdownMenuItem<String>>[]; if (widget.showAllOption) { dropdownItems.add( DropdownMenuItem( value: "all", child: Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: TextViewWidget( text: "All", textStyle: CustomTextStyle.smallText.copyWith( fontWeight: FontWeight.w500, color: Colors.white, ), textOverflow: TextOverflow.ellipsis, ), ), ), ); } if (widget.items.isNotEmpty) { dropdownItems.addAll( widget.items.map((item) { return DropdownMenuItem<String>( value: widget.getValue(item), child: Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: TextViewWidget( text: widget.getLabel(item), textStyle: CustomTextStyle.smallText.copyWith( fontWeight: FontWeight.w500, color: Colors.white, ), textOverflow: TextOverflow.ellipsis, ), ), ); }).toList(), ); } // 确保selectedValue与dropdownItems中的项严格匹配,避免无效值 String? selectedValue; if (widget.value != null) { final isValueValid = dropdownItems.any((item) => item.value == widget.value); selectedValue = isValueValid ? widget.value : (widget.showAllOption ? "all" : null); } else { selectedValue = widget.showAllOption ? "all" : null; } return DropdownButtonFormField2<String>( value: selectedValue, isExpanded: true, decoration: widget.isAlternativeDesign ? InputDecoration( filled: true, fillColor: AppColor.black500, contentPadding: const EdgeInsets.all(10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide( width: 1, color: AppColor.borderSecondary, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide( width: 1, color: AppColor.borderSecondary, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide( width: 1, color: AppColor.borderPrimary, ), ), ) : const InputDecoration( border: OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: AppColor.borderSecondary, width: 1, ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: AppColor.borderPrimary, width: 1), ), ), hint: Text( widget.items.isEmpty ? "None" : widget.hintText, style: CustomTextStyle.smallText.copyWith( fontWeight: FontWeight.w500, color: Colors.white, ), overflow: TextOverflow.ellipsis, ), items: dropdownItems, selectedItemBuilder: (context) { return dropdownItems.map((item) { String displayText; if (item.value == "all") { displayText = "All"; } else { if (widget.items.isEmpty) { displayText = ""; } else { final matchedItem = widget.items.firstWhere( (e) => widget.getValue(e) == item.value, orElse: () => widget.items.first, ); displayText = widget.getLabel(matchedItem); } } return Align( alignment: Alignment.centerLeft, child: Text( displayText, style: CustomTextStyle.smallText.copyWith( fontWeight: FontWeight.w500, color: Colors.white, ), overflow: TextOverflow.ellipsis, ), ); }).toList(); }, onChanged: widget.onChanged, dropdownStyleData: DropdownStyleData( padding: EdgeInsets.zero, scrollPadding: EdgeInsets.zero, maxHeight: widget.menuMaxHeight ?? 200, width: widget.width, decoration: BoxDecoration( color: widget.dropdownColor, borderRadius: BorderRadius.circular(4), ), isOverButton: false, scrollbarTheme: ScrollbarThemeData( thumbColor: WidgetStateProperty.all(Colors.white.withOpacity(0.5)), ), ), onMenuStateChange: _onMenuStateChange, iconStyleData: IconStyleData( icon: RotationTransition( turns: Tween(begin: 0.0, end: 0.5).animate(_controller), child: Icon( Icons.keyboard_arrow_down, color: widget.iconColor, ), ), ), ); } }
关键修复点说明
- 空列表安全防护:生成dropdownItems时仅在
widget.items非空时添加对应项,selectedItemBuilder中先判断列表是否为空,避免空列表调用first方法报错。 - selectedValue严格校验:新增无效值校验,确保
widget.value是dropdownItems中存在的有效值,否则自动 fallback 到「all」选项(如果开启),彻底避免选中状态匹配失败。 - 补全语法错误:完善
ScrollbarThemeData的thumbColor配置,修复原代码中的语法不完整问题。 - 强化「All」选项显示逻辑:直接通过item.value判断是否为「All」选项,避免冗余匹配逻辑导致的显示偏差。
内容来源于stack exchange
相关产品推荐
相关产品推荐

