From 829ac2803636e8f5968f64ed7f67c5d967aab344 Mon Sep 17 00:00:00 2001 From: Henri Sauer Date: Thu, 16 May 2024 23:20:43 +0200 Subject: [PATCH 1/4] Added the Option dropdownOnlyBeneathButton to DropdownButton2, which forces the dropdown menu to only be displayed beneath the dropdown button, even if it means to make the dropdown menu scrollable --- .../lib/src/dropdown_button2.dart | 9 +++++++++ .../lib/src/dropdown_route.dart | 20 +++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/dropdown_button2/lib/src/dropdown_button2.dart b/packages/dropdown_button2/lib/src/dropdown_button2.dart index c530117..fefb080 100644 --- a/packages/dropdown_button2/lib/src/dropdown_button2.dart +++ b/packages/dropdown_button2/lib/src/dropdown_button2.dart @@ -122,6 +122,7 @@ class DropdownButton2 extends StatefulWidget { this.barrierLabel, this.barrierCoversButton = true, this.openDropdownListenable, + this.dropdownOnlyBeneathButton = false, // When adding new arguments, consider adding similar arguments to // DropdownButtonFormField. }) : assert( @@ -163,6 +164,7 @@ class DropdownButton2 extends StatefulWidget { this.barrierCoversButton = true, this.barrierLabel, this.openDropdownListenable, + this.dropdownOnlyBeneathButton = false, required InputDecoration inputDecoration, required bool isEmpty, required bool isFocused, @@ -383,6 +385,12 @@ class DropdownButton2 extends StatefulWidget { /// ``` final Listenable? openDropdownListenable; + /// If set, the dropdown menu will only be displayed beneath the button, + /// even if it means to make the menu scrollable. + /// + /// Defaults to false + final bool dropdownOnlyBeneathButton; + final InputDecoration? _inputDecoration; final bool _isEmpty; @@ -597,6 +605,7 @@ class _DropdownButton2State extends State> menuItemStyle: _menuItemStyle, searchData: _searchData, dropdownSeparator: separator, + dropdownOnlyBeneathButton: widget.dropdownOnlyBeneathButton, ); _isMenuOpen.value = true; diff --git a/packages/dropdown_button2/lib/src/dropdown_route.dart b/packages/dropdown_button2/lib/src/dropdown_route.dart index 757261b..ebe9056 100644 --- a/packages/dropdown_button2/lib/src/dropdown_route.dart +++ b/packages/dropdown_button2/lib/src/dropdown_route.dart @@ -18,6 +18,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { required this.dropdownStyle, required this.menuItemStyle, required this.searchData, + required this.dropdownOnlyBeneathButton, this.dropdownSeparator, }) : itemHeights = addSeparatorsHeights( itemHeights: items.map((item) => item.height).toList(), @@ -60,6 +61,8 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { final bool barrierCoversButton; + final bool dropdownOnlyBeneathButton; + final FocusScopeNode _childNode = FocusScopeNode(debugLabel: 'Child'); @override @@ -142,7 +145,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { int index, ) { double maxHeight = - getMenuAvailableHeight(availableHeight, mediaQueryPadding); + getMenuAvailableHeight(availableHeight, mediaQueryPadding, buttonRect); // If a preferred MaxHeight is set by the user, use it instead of the available maxHeight. final double? preferredMaxHeight = dropdownStyle.maxHeight; if (preferredMaxHeight != null) { @@ -217,11 +220,16 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { double getMenuAvailableHeight( double availableHeight, EdgeInsets mediaQueryPadding, + Rect rect, ) { + if (!dropdownOnlyBeneathButton) { + return math.max( + 0.0, + availableHeight - mediaQueryPadding.vertical - _kMenuItemHeight, + ); + } return math.max( - 0.0, - availableHeight - mediaQueryPadding.vertical - _kMenuItemHeight, - ); + 0.0, availableHeight - rect.bottom - mediaQueryPadding.vertical); } } @@ -323,8 +331,8 @@ class _DropdownMenuRouteLayout extends SingleChildLayoutDelegate { @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { final double? itemWidth = route.dropdownStyle.width; - double maxHeight = - route.getMenuAvailableHeight(availableHeight, mediaQueryPadding); + double maxHeight = route.getMenuAvailableHeight( + availableHeight, mediaQueryPadding, buttonRect); final double? preferredMaxHeight = route.dropdownStyle.maxHeight; if (preferredMaxHeight != null && preferredMaxHeight <= maxHeight) { maxHeight = preferredMaxHeight; From fb4a2b4628f8a4a9c54facf2119257ee2be6a439 Mon Sep 17 00:00:00 2001 From: Henri Sauer Date: Fri, 17 May 2024 12:02:32 +0200 Subject: [PATCH 2/4] Renamed dropdownOnlyBeneathButton to dropdownOnlyBelowButton, added Documentation in README.md --- README.md | 60 ++++++++++--------- .../lib/src/dropdown_button2.dart | 10 ++-- .../lib/src/dropdown_route.dart | 10 ++-- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index f4c0b71..0be2f49 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ customize to your needs. ## Features - Dropdown menu always open below the button "as long as it's possible otherwise it'll open to the - end of the screen" and you can edit its position by using the offset parameter. + end of the screen" and you can edit its position by using the offset and dropdownOnlyBelowButton + parameter. - You can control how (button, button's icon, dropdown menu and menu items) will be displayed "read Options below". - You can align (hint & value) and customize them. @@ -65,34 +66,35 @@ customize to your needs. ### DropdownButton2: -| Option | Description | Type | Required | -| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------- | :------: | -| [items](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/items.html) | The list of items the user can select | List> | Yes | -| [selectedItemBuilder](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/selectedItemBuilder.html) | A builder to customize how the selected item will be displayed on the button | DropdownButtonBuilder | No | -| [valueListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/valueListenable.html) | A [ValueListenable] that represents the value of the currently selected [DropdownItem]. | ValueListenable? | No | -| [multiValueListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/multiValueListenable.html) | A [ValueListenable] that represents a list of the currently selected [DropdownItem]s | ValueListenable>? | No | -| [hint](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/hint.html) | The placeholder displayed before the user choose an item | Widget | No | -| [disabledHint](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/disabledHint.html) | The placeholder displayed if the dropdown is disabled | Widget | No | -| [onChanged](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/onChanged.html) | Called when the user selects an item | ValueChanged | No | -| [onMenuStateChange](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/onMenuStateChange.html) | Called when the dropdown menu opens or closes | OnMenuStateChangeFn | No | -| [style](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/style.html) | The text style to use for text in the dropdown button and the dropdown menu | TextStyle | No | -| [underline](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/underline.html) | The widget to use for drawing the drop-down button's underline | Widget | No | -| [isDense](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/isDense.html) | Reduce the button's height | bool | No | -| [isExpanded](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/isExpanded.html) | Makes the button's inner contents expanded (set true to avoid long text overflowing) | bool | No | -| [alignment](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/alignment.html) | Defines how the hint or the selected item is positioned within the button | AlignmentGeometry | No | -| [buttonStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/buttonStyleData.html) | Used to configure the theme of the button | ButtonStyleData | No | -| [iconStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/iconStyleData.html) | Used to configure the theme of the button's icon | IconStyleData | No | -| [dropdownStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownStyleData.html) | Used to configure the theme of the dropdown menu | DropdownStyleData | No | -| [menuItemStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/menuItemStyleData.html) | Used to configure the theme of the dropdown menu items | MenuItemStyleData | No | -| [dropdownSearchData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownSearchData.html) | Used to configure searchable dropdowns | DropdownSearchData | No | -| [dropdownSeparator](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownSeparator.html) | Adds separator widget to the dropdown menu | DropdownSeparator | No | -| [customButton](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/customButton.html) | Uses custom widget like icon,image,etc.. instead of the default button | Widget | No | -| [openWithLongPress](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/openWithLongPress.html) | Opens the dropdown menu on long-pressing instead of tapping | bool | No | -| [barrierDismissible](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierDismissible.html) | Whether you can dismiss this route by tapping the modal barrier | bool | No | -| [barrierColor](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierColor.html) | The color to use for the modal barrier. If this is null, the barrier will be transparent | Color | No | -| [barrierLabel](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierLabel.html) | The semantic label used for a dismissible barrier | String | No | -| [barrierCoversButton](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierCoversButton.html) | Specifies whether the modal barrier should cover the dropdown button or not. | bool | No | -| [openDropdownListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/openDropdownListenable.html) | A [Listenable] that can be used to programmatically open the dropdown menu. | Listenable? | No | +| Option | Description | Type | Required | +|------------------------------------------------------------------------------------------------------------------------------------------------| ---------------------------------------------------------------------------------------- | -------------------------- | :------: | +| [items](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/items.html) | The list of items the user can select | List> | Yes | +| [selectedItemBuilder](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/selectedItemBuilder.html) | A builder to customize how the selected item will be displayed on the button | DropdownButtonBuilder | No | +| [valueListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/valueListenable.html) | A [ValueListenable] that represents the value of the currently selected [DropdownItem]. | ValueListenable? | No | +| [multiValueListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/multiValueListenable.html) | A [ValueListenable] that represents a list of the currently selected [DropdownItem]s | ValueListenable>? | No | +| [hint](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/hint.html) | The placeholder displayed before the user choose an item | Widget | No | +| [disabledHint](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/disabledHint.html) | The placeholder displayed if the dropdown is disabled | Widget | No | +| [onChanged](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/onChanged.html) | Called when the user selects an item | ValueChanged | No | +| [onMenuStateChange](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/onMenuStateChange.html) | Called when the dropdown menu opens or closes | OnMenuStateChangeFn | No | +| [style](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/style.html) | The text style to use for text in the dropdown button and the dropdown menu | TextStyle | No | +| [underline](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/underline.html) | The widget to use for drawing the drop-down button's underline | Widget | No | +| [isDense](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/isDense.html) | Reduce the button's height | bool | No | +| [isExpanded](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/isExpanded.html) | Makes the button's inner contents expanded (set true to avoid long text overflowing) | bool | No | +| [alignment](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/alignment.html) | Defines how the hint or the selected item is positioned within the button | AlignmentGeometry | No | +| [buttonStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/buttonStyleData.html) | Used to configure the theme of the button | ButtonStyleData | No | +| [iconStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/iconStyleData.html) | Used to configure the theme of the button's icon | IconStyleData | No | +| [dropdownStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownStyleData.html) | Used to configure the theme of the dropdown menu | DropdownStyleData | No | +| [menuItemStyleData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/menuItemStyleData.html) | Used to configure the theme of the dropdown menu items | MenuItemStyleData | No | +| [dropdownSearchData](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownSearchData.html) | Used to configure searchable dropdowns | DropdownSearchData | No | +| [dropdownSeparator](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownSeparator.html) | Adds separator widget to the dropdown menu | DropdownSeparator | No | +| [customButton](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/customButton.html) | Uses custom widget like icon,image,etc.. instead of the default button | Widget | No | +| [openWithLongPress](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/openWithLongPress.html) | Opens the dropdown menu on long-pressing instead of tapping | bool | No | +| [barrierDismissible](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierDismissible.html) | Whether you can dismiss this route by tapping the modal barrier | bool | No | +| [barrierColor](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierColor.html) | The color to use for the modal barrier. If this is null, the barrier will be transparent | Color | No | +| [barrierLabel](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierLabel.html) | The semantic label used for a dismissible barrier | String | No | +| [barrierCoversButton](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/barrierCoversButton.html) | Specifies whether the modal barrier should cover the dropdown button or not. | bool | No | +| [openDropdownListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/openDropdownListenable.html) | A [Listenable] that can be used to programmatically open the dropdown menu. | Listenable? | No | +| [dropdownOnlyBelowButton](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/dropdownOnlyBelowButton.html) | Forces the dropdown menu to be only displayed beneath the button. | bool | No | #### Subclass ButtonStyleData: diff --git a/packages/dropdown_button2/lib/src/dropdown_button2.dart b/packages/dropdown_button2/lib/src/dropdown_button2.dart index fefb080..8e73080 100644 --- a/packages/dropdown_button2/lib/src/dropdown_button2.dart +++ b/packages/dropdown_button2/lib/src/dropdown_button2.dart @@ -122,7 +122,7 @@ class DropdownButton2 extends StatefulWidget { this.barrierLabel, this.barrierCoversButton = true, this.openDropdownListenable, - this.dropdownOnlyBeneathButton = false, + this.dropdownOnlyBelowButton = false, // When adding new arguments, consider adding similar arguments to // DropdownButtonFormField. }) : assert( @@ -164,7 +164,7 @@ class DropdownButton2 extends StatefulWidget { this.barrierCoversButton = true, this.barrierLabel, this.openDropdownListenable, - this.dropdownOnlyBeneathButton = false, + this.dropdownOnlyBelowButton = false, required InputDecoration inputDecoration, required bool isEmpty, required bool isFocused, @@ -385,11 +385,11 @@ class DropdownButton2 extends StatefulWidget { /// ``` final Listenable? openDropdownListenable; - /// If set, the dropdown menu will only be displayed beneath the button, + /// If set, the dropdown menu will only be displayed below the button, /// even if it means to make the menu scrollable. /// /// Defaults to false - final bool dropdownOnlyBeneathButton; + final bool dropdownOnlyBelowButton; final InputDecoration? _inputDecoration; @@ -605,7 +605,7 @@ class _DropdownButton2State extends State> menuItemStyle: _menuItemStyle, searchData: _searchData, dropdownSeparator: separator, - dropdownOnlyBeneathButton: widget.dropdownOnlyBeneathButton, + dropdownOnlyBelowButton: widget.dropdownOnlyBelowButton, ); _isMenuOpen.value = true; diff --git a/packages/dropdown_button2/lib/src/dropdown_route.dart b/packages/dropdown_button2/lib/src/dropdown_route.dart index ebe9056..aaed528 100644 --- a/packages/dropdown_button2/lib/src/dropdown_route.dart +++ b/packages/dropdown_button2/lib/src/dropdown_route.dart @@ -18,7 +18,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { required this.dropdownStyle, required this.menuItemStyle, required this.searchData, - required this.dropdownOnlyBeneathButton, + required this.dropdownOnlyBelowButton, this.dropdownSeparator, }) : itemHeights = addSeparatorsHeights( itemHeights: items.map((item) => item.height).toList(), @@ -61,7 +61,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { final bool barrierCoversButton; - final bool dropdownOnlyBeneathButton; + final bool dropdownOnlyBelowButton; final FocusScopeNode _childNode = FocusScopeNode(debugLabel: 'Child'); @@ -220,16 +220,16 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { double getMenuAvailableHeight( double availableHeight, EdgeInsets mediaQueryPadding, - Rect rect, + Rect buttonRect, ) { - if (!dropdownOnlyBeneathButton) { + if (!dropdownOnlyBelowButton) { return math.max( 0.0, availableHeight - mediaQueryPadding.vertical - _kMenuItemHeight, ); } return math.max( - 0.0, availableHeight - rect.bottom - mediaQueryPadding.vertical); + 0.0, availableHeight - buttonRect.bottom - mediaQueryPadding.vertical); } } From 511b546da9d8b917d9b05d250ad0bc1850b58d0e Mon Sep 17 00:00:00 2001 From: Henri Sauer Date: Sun, 19 May 2024 11:02:20 +0200 Subject: [PATCH 3/4] added the dropdownOnlyBelowButton option to DropdownButtonFormField2 --- packages/dropdown_button2/lib/src/dropdown_button2.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/dropdown_button2/lib/src/dropdown_button2.dart b/packages/dropdown_button2/lib/src/dropdown_button2.dart index 8e73080..6599440 100644 --- a/packages/dropdown_button2/lib/src/dropdown_button2.dart +++ b/packages/dropdown_button2/lib/src/dropdown_button2.dart @@ -13,11 +13,17 @@ import 'package:flutter/services.dart'; import 'seperated_sliver_child_builder_delegate.dart'; part 'dropdown_style_data.dart'; + part 'dropdown_route.dart'; + part 'dropdown_menu.dart'; + part 'dropdown_menu_item.dart'; + part 'dropdown_menu_separators.dart'; + part 'enums.dart'; + part 'utils.dart'; const Duration _kDropdownMenuDuration = Duration(milliseconds: 300); @@ -957,6 +963,7 @@ class DropdownButtonFormField2 extends FormField { Widget? customButton, bool openWithLongPress = false, bool barrierDismissible = true, + bool dropdownOnlyBelowButton = false, Color? barrierColor, String? barrierLabel, Listenable? openDropdownListenable, @@ -1039,6 +1046,7 @@ class DropdownButtonFormField2 extends FormField { inputDecoration: effectiveDecoration, isEmpty: isEmpty, isFocused: Focus.of(context).hasFocus, + dropdownOnlyBelowButton: dropdownOnlyBelowButton, ), ), ); From 0273893d6fe85378435a85244285a42087fa5724 Mon Sep 17 00:00:00 2001 From: Henri Sauer Date: Wed, 29 May 2024 02:05:15 +0200 Subject: [PATCH 4/4] Fixed bug with dropdownOnlyBelowButton: menu didn't extend to max. possible size if needed --- packages/dropdown_button2/lib/src/dropdown_route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dropdown_button2/lib/src/dropdown_route.dart b/packages/dropdown_button2/lib/src/dropdown_route.dart index aaed528..87489f0 100644 --- a/packages/dropdown_button2/lib/src/dropdown_route.dart +++ b/packages/dropdown_button2/lib/src/dropdown_route.dart @@ -229,7 +229,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { ); } return math.max( - 0.0, availableHeight - buttonRect.bottom - mediaQueryPadding.vertical); + 0.0, availableHeight - buttonRect.bottom - mediaQueryPadding.bottom); } }