diff --git a/README.md b/README.md index f82daba..b150ec8 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 534daa9..b2028a8 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); @@ -122,6 +128,7 @@ class DropdownButton2 extends StatefulWidget { this.barrierLabel, this.barrierCoversButton = true, this.openDropdownListenable, + this.dropdownOnlyBelowButton = false, // When adding new arguments, consider adding similar arguments to // DropdownButtonFormField. }) : assert( @@ -163,6 +170,7 @@ class DropdownButton2 extends StatefulWidget { this.barrierCoversButton = true, this.barrierLabel, this.openDropdownListenable, + this.dropdownOnlyBelowButton = false, required InputDecoration inputDecoration, required bool isEmpty, required bool isFocused, @@ -383,6 +391,12 @@ class DropdownButton2 extends StatefulWidget { /// ``` final Listenable? openDropdownListenable; + /// 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 dropdownOnlyBelowButton; + final InputDecoration? _inputDecoration; final bool _isEmpty; @@ -592,6 +606,7 @@ class _DropdownButton2State extends State> menuItemStyle: _menuItemStyle, searchData: _searchData, dropdownSeparator: separator, + dropdownOnlyBelowButton: widget.dropdownOnlyBelowButton, ); _isMenuOpen.value = true; @@ -943,6 +958,7 @@ class DropdownButtonFormField2 extends FormField { Widget? customButton, bool openWithLongPress = false, bool barrierDismissible = true, + bool dropdownOnlyBelowButton = false, Color? barrierColor, String? barrierLabel, Listenable? openDropdownListenable, @@ -1025,6 +1041,7 @@ class DropdownButtonFormField2 extends FormField { inputDecoration: effectiveDecoration, isEmpty: isEmpty, isFocused: Focus.of(context).hasFocus, + dropdownOnlyBelowButton: dropdownOnlyBelowButton, ), ), ); diff --git a/packages/dropdown_button2/lib/src/dropdown_route.dart b/packages/dropdown_button2/lib/src/dropdown_route.dart index 757261b..87489f0 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.dropdownOnlyBelowButton, 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 dropdownOnlyBelowButton; + 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 buttonRect, ) { + if (!dropdownOnlyBelowButton) { + return math.max( + 0.0, + availableHeight - mediaQueryPadding.vertical - _kMenuItemHeight, + ); + } return math.max( - 0.0, - availableHeight - mediaQueryPadding.vertical - _kMenuItemHeight, - ); + 0.0, availableHeight - buttonRect.bottom - mediaQueryPadding.bottom); } } @@ -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;