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

Flutter DropdownButton2自定义下拉组件选中「All」后显示文本与值不一致问题

Flutter DropdownButton2自定义下拉组件选中「All」后显示文本与值不一致问题

你好,我帮你排查了这个CustomDropdown组件的问题,选中「All」后显示文本与值不一致的核心原因是几个逻辑细节的漏洞,下面是具体分析和修复方案:

问题根源分析

  1. 空列表的Fallback逻辑错误:当widget.items为空且showAllOption为true时,selectedItemBuilder中firstWhere的orElse: () => widget.items.first会直接抛出StateError,因为空列表调用first方法是非法的,这会导致组件崩溃或显示异常。
  2. selectedValue匹配不严谨:原代码中selectedValue直接使用widget.value ?? (widget.showAllOption ? "all" : null),如果widget.value是一个不存在于dropdownItems中的无效值,会导致选中状态匹配失败,显示异常。
  3. 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,
          ),
        ),
      ),
    );
  }
}

关键修复点说明

  1. 空列表安全防护:生成dropdownItems时仅在widget.items非空时添加对应项,selectedItemBuilder中先判断列表是否为空,避免空列表调用first方法报错。
  2. selectedValue严格校验:新增无效值校验,确保widget.value是dropdownItems中存在的有效值,否则自动 fallback 到「all」选项(如果开启),彻底避免选中状态匹配失败。
  3. 补全语法错误:完善ScrollbarThemeData的thumbColor配置,修复原代码中的语法不完整问题。
  4. 强化「All」选项显示逻辑:直接通过item.value判断是否为「All」选项,避免冗余匹配逻辑导致的显示偏差。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 08:58:08