@@ -86,6 +86,25 @@ class _WoxDropdownButtonState<T> extends State<WoxDropdownButton<T>> {
8686 );
8787 }
8888
89+ Color _getDropdownBackgroundColor () {
90+ final baseDropdownBg = widget.dropdownColor ?? getThemeActiveBackgroundColor ();
91+ return baseDropdownBg.withAlpha (255 );
92+ }
93+
94+ double _contrastRatio (Color foreground, Color background) {
95+ final foregroundLuminance = foreground.computeLuminance ();
96+ final backgroundLuminance = background.computeLuminance ();
97+ final bright = foregroundLuminance > backgroundLuminance ? foregroundLuminance : backgroundLuminance;
98+ final dark = foregroundLuminance > backgroundLuminance ? backgroundLuminance : foregroundLuminance;
99+ return (bright + 0.05 ) / (dark + 0.05 );
100+ }
101+
102+ Color _getReadableTextColor (Color background) {
103+ const darkText = Colors .black87;
104+ const lightText = Colors .white;
105+ return _contrastRatio (lightText, background) >= _contrastRatio (darkText, background) ? lightText : darkText;
106+ }
107+
89108 void _markOverlayNeedsBuildSafely () {
90109 if (_overlayEntry == null ) {
91110 return ;
@@ -237,7 +256,14 @@ class _WoxDropdownButtonState<T> extends State<WoxDropdownButton<T>> {
237256
238257 void _showFilterableMenu () {
239258 final activeTextColor = getThemeActiveTextColor ();
240- final dropdownBg = widget.dropdownColor ?? getThemeActiveBackgroundColor ().withAlpha (255 );
259+ final dropdownBg = _getDropdownBackgroundColor ();
260+ final searchBg =
261+ dropdownBg.computeLuminance () > 0.45
262+ ? Color .alphaBlend (Colors .black.withValues (alpha: 0.08 ), dropdownBg)
263+ : Color .alphaBlend (Colors .white.withValues (alpha: 0.08 ), dropdownBg);
264+ final searchTextColor = _getReadableTextColor (searchBg);
265+ final searchHintColor = searchTextColor.withValues (alpha: 0.55 );
266+ final searchDividerColor = searchTextColor.withValues (alpha: 0.20 );
241267 final borderColor = getThemeSubTextColor ();
242268
243269 final RenderBox renderBox = context.findRenderObject () as RenderBox ;
@@ -261,6 +287,7 @@ class _WoxDropdownButtonState<T> extends State<WoxDropdownButton<T>> {
261287 borderRadius: BorderRadius .circular (4 ),
262288 color: dropdownBg,
263289 child: Container (
290+ clipBehavior: Clip .antiAlias,
264291 constraints: BoxConstraints (maxHeight: widget.menuMaxHeight ?? 300 ),
265292 decoration: BoxDecoration (border: Border .all (color: borderColor), borderRadius: BorderRadius .circular (4 )),
266293 child: Column (
@@ -269,47 +296,65 @@ class _WoxDropdownButtonState<T> extends State<WoxDropdownButton<T>> {
269296 // Filter text field
270297 Container (
271298 padding: const EdgeInsets .symmetric (horizontal: 8 , vertical: 4 ),
272- decoration: BoxDecoration (border: Border (bottom: BorderSide (color: borderColor))),
273- child: TextField (
274- controller: _filterController,
275- focusNode: _filterFocusNode,
276- autofocus: true ,
277- style: TextStyle (color: activeTextColor, fontSize: widget.fontSize).useSystemChineseFont (),
278- decoration: InputDecoration (
279- hintText: 'Filter...' ,
280- hintStyle: TextStyle (color: activeTextColor.withValues (alpha: 0.5 ), fontSize: widget.fontSize).useSystemChineseFont (),
281- border: InputBorder .none,
282- isDense: true ,
283- contentPadding: const EdgeInsets .symmetric (horizontal: 4 , vertical: 8 ),
284- prefixIcon: Icon (Icons .search, size: 16 , color: activeTextColor.withValues (alpha: 0.7 )),
299+ decoration: BoxDecoration (
300+ color: searchBg,
301+ border: Border (bottom: BorderSide (color: searchDividerColor)),
302+ borderRadius: const BorderRadius .vertical (top: Radius .circular (4 )),
303+ ),
304+ child: Focus (
305+ onKeyEvent: (node, event) {
306+ if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey .escape) {
307+ _removeOverlay ();
308+ return KeyEventResult .handled;
309+ }
310+ return KeyEventResult .ignored;
311+ },
312+ child: TextField (
313+ controller: _filterController,
314+ focusNode: _filterFocusNode,
315+ autofocus: true ,
316+ textAlignVertical: TextAlignVertical .center,
317+ style: TextStyle (color: searchTextColor, fontSize: widget.fontSize).useSystemChineseFont (),
318+ decoration: InputDecoration (
319+ hintText: 'Filter...' ,
320+ hintStyle: TextStyle (color: searchHintColor, fontSize: widget.fontSize).useSystemChineseFont (),
321+ border: InputBorder .none,
322+ isDense: true ,
323+ contentPadding: const EdgeInsets .symmetric (vertical: 8 ),
324+ prefixIcon: Padding (padding: const EdgeInsets .only (left: 4 , right: 6 ), child: Icon (Icons .search, size: 16 , color: searchHintColor)),
325+ prefixIconConstraints: const BoxConstraints (minWidth: 22 , minHeight: 22 ),
326+ ),
327+ onChanged: _filterItems,
285328 ),
286- onChanged: _filterItems,
287329 ),
288330 ),
289331 // Filtered items list
290332 Flexible (
291- child: ListView .builder (
292- shrinkWrap: true ,
293- padding: EdgeInsets .zero,
294- itemCount: _filteredItems.length,
295- itemBuilder: (context, index) {
296- final item = _filteredItems[index];
297- final isSelected = item.value == widget.value;
298- return _buildNoRippleInkWell (
299- onTap: () {
300- widget.onChanged? .call (item.value);
301- _removeOverlay ();
302- },
303- child: Container (
304- padding: const EdgeInsets .symmetric (horizontal: 12 , vertical: 10 ),
305- color: isSelected ? activeTextColor.withValues (alpha: 0.1 ) : null ,
306- child: DefaultTextStyle (
307- style: TextStyle (color: activeTextColor, fontSize: widget.fontSize).useSystemChineseFont (),
308- child: _buildDropdownMenuItem (item, activeTextColor),
333+ child: Container (
334+ color: dropdownBg,
335+ child: ListView .builder (
336+ shrinkWrap: true ,
337+ padding: EdgeInsets .zero,
338+ itemCount: _filteredItems.length,
339+ itemBuilder: (context, index) {
340+ final item = _filteredItems[index];
341+ final isSelected = item.value == widget.value;
342+ return _buildNoRippleInkWell (
343+ onTap: () {
344+ widget.onChanged? .call (item.value);
345+ _removeOverlay ();
346+ },
347+ child: Container (
348+ padding: const EdgeInsets .symmetric (horizontal: 12 , vertical: 10 ),
349+ color: isSelected ? activeTextColor.withValues (alpha: 0.1 ) : null ,
350+ child: DefaultTextStyle (
351+ style: TextStyle (color: activeTextColor, fontSize: widget.fontSize).useSystemChineseFont (),
352+ child: _buildDropdownMenuItem (item, activeTextColor),
353+ ),
309354 ),
310- ),
311- );
312- } ,
355+ );
356+ },
357+ ) ,
313358 ),
314359 ),
315360 ],
@@ -357,7 +402,7 @@ class _WoxDropdownButtonState<T> extends State<WoxDropdownButton<T>> {
357402
358403 void _showMultiSelectMenu () {
359404 final activeTextColor = getThemeActiveTextColor ();
360- final dropdownBg = widget.dropdownColor ?? getThemeActiveBackgroundColor (). withAlpha ( 255 );
405+ final dropdownBg = _getDropdownBackgroundColor ( );
361406 final borderColor = getThemeSubTextColor ();
362407
363408 final RenderBox renderBox = context.findRenderObject () as RenderBox ;
@@ -493,7 +538,7 @@ class _WoxDropdownButtonState<T> extends State<WoxDropdownButton<T>> {
493538 Widget build (BuildContext context) {
494539 final textColor = getThemeTextColor ();
495540 final activeTextColor = getThemeActiveTextColor ();
496- final dropdownBg = widget.dropdownColor ?? getThemeActiveBackgroundColor (). withAlpha ( 255 );
541+ final dropdownBg = _getDropdownBackgroundColor ( );
497542 final borderColor = getThemeSubTextColor ();
498543
499544 if (widget.multiSelect) {
0 commit comments