The issue you're encountering—where the upper dropdown menu hides behind the lower dropdown when scrolled—occurs because of how Flutter handles stacking and clipping in a Column within a scrollable widget (e.g., SingleChildScrollView or ListView). By default, dropdown menus in Flutter's DropdownMenu are rendered in a separate overlay layer, but their positioning and stacking can be affected by the parent widget's clipping behavior or z-index when scrolling.
The problem is more pronounced when:
Two DropdownMenu widgets are placed in a Column.
The parent widget is scrollable, causing the dropdown's overlay to be clipped or misaligned.
The Stack order of the dropdowns is not properly managed, leading to one dropdown's overlay appearing behind another.
Solution
To fix this, you need to ensure that the dropdown menu's overlay is rendered above all other widgets and is not clipped by the scrollable parent. You can achieve this by:
Using ClipBehavior: Disable clipping in the parent scrollable widget (e.g., SingleChildScrollView) to allow the dropdown menu to overflow without being hidden.
Adjusting MenuStyle: Explicitly set the elevation in the MenuStyle to ensure the dropdown menu has a higher z-index.
Using Stack for Overlap Control: If the issue persists, wrap the dropdowns in a Stack to control their rendering order explicitly.
Ensuring Unique Keys: Verify that each DropdownMenu has a unique key to avoid rendering conflicts.
Here’s an updated version of your myDropDown widget with fixes:
Widget myDropDown({
required String title,
required List<DropdownMenuEntry> entries,
bool? hideTitle,
bool? autoWidth,
double? width,
int? Function(List<DropdownMenuEntry<dynamic>>, String)? searchCallback,
bool? requestFocus,
TextEditingController? textController,
void Function(dynamic)? onSelected,
bool? enabled,
dynamic initialSelection,
FocusNode? focusNode,
bool showHint = true,
bool hasData = false,
VoidCallback? hasDataOnTap,
}) {
return Padding(
padding: EdgeInsets.only(bottom: 12.0.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!(hideTitle ?? false)) ...[
CustomText(
title: title,
fontSize: 14.sp,
bold: true,
color: AppColors.primaryColor,
),
12.verticalSpace,
],
DropdownMenu(
key: UniqueKey(), // Ensure unique key for each dropdown
initialSelection: initialSelection,
enabled: enabled ?? true,
enableSearch: false,
focusNode: focusNode,
onSelected: onSelected,
requestFocusOnTap: requestFocus ?? false,
controller: textController,
menuHeight: Get.height * 0.5,
searchCallback: searchCallback,
expandedInsets: EdgeInsets.zero, // Simplified
hintText: showHint ? "Select" : null,
menuStyle: MenuStyle(
backgroundColor: const WidgetStatePropertyAll(AppColors.white),
elevation: const WidgetStatePropertyAll(8), // Higher z-index
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.sp),
),
),
),
trailingIcon: hasData
? InkWell(
onTap: hasDataOnTap,
child: const Icon(Icons.close),
)
: const Icon(Icons.keyboard_arrow_down),
selectedTrailingIcon: const Icon(Icons.keyboard_arrow_up),
inputDecorationTheme: InputDecorationTheme(
isDense: true,
fillColor: AppColors.white,
filled: true,
contentPadding: EdgeInsets.symmetric(horizontal: 15.w),
border: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.iconGrey),
borderRadius: BorderRadius.circular(8.sp),
),
),
dropdownMenuEntries: entries,
),
],
),
);
}
Example Usage in a Scrollable Column
To ensure the dropdowns work correctly when scrolled, wrap them in a SingleChildScrollView with clipBehavior set to Clip.none:
Widget build(BuildContext context) {
return SingleChildScrollView(
clipBehavior: Clip.none, // Prevent clipping of dropdown menus
child: Column(
children: [
myDropDown(
title: "First Dropdown",
entries: [
DropdownMenuEntry(value: "1", label: "Option 1"),
DropdownMenuEntry(value: "2", label: "Option 2"),
],
),
myDropDown(
title: "Second Dropdown",
entries: [
DropdownMenuEntry(value: "3", label: "Option 3"),
DropdownMenuEntry(value: "4", label: "Option 4"),
],
),
],
),
);
}
Explanation of Fixes
ClipBehavior:
Setting clipBehavior: Clip.none on the SingleChildScrollView ensures that the dropdown menu’s overlay is not clipped when it overflows the parent’s bounds.
Elevation:
Adding elevation: WidgetStatePropertyAll(8) in MenuStyle ensures the dropdown menu appears above other widgets in the stack.
Unique Keys:
Using UniqueKey() ensures each DropdownMenu is treated as a distinct widget, preventing rendering conflicts.
Simplified Insets:
Changed expandedInsets: EdgeInsets.all(0.sp) to EdgeInsets.zero for clarity and consistency.
Additional Suggestions
Test with Scrolling: Test the dropdowns in a scrollable context with various scroll positions to ensure the overlay behaves correctly.
Use a Stack if Needed: If the issue persists, consider wrapping the Column in a Stack and controlling the dropdowns’ z-index explicitly:
Stack(
children: [
Positioned.fill(
child: SingleChildScrollView(
child: Column(
children: [
myDropDown(...),
myDropDown(...),
],
),
),
),
],
);
Check Parent Constraints: Ensure the parent widget (e.g., Scaffold or Container) does not impose tight constraints that affect the dropdown’s overlay positioning.
Debug Overlay: Use Flutter’s debugPaintLayerBordersEnabled = true in main.dart to visualize the dropdown’s overlay and identify clipping issues.
Why It Happens Only When Scrolled?
When the page is scrolled, the scrollable widget’s viewport changes, and Flutter may clip the dropdown’s overlay if it extends beyond the visible area. This is why disabling clipping and ensuring proper elevation resolves the issue.
This solution should prevent the upper dropdown from hiding behind the lower one, even when scrolled.