Instantly share code, notes, and snippets.
Last active
January 24, 2025 11:23
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save lsaudon/0091b57302c3700651ad6ccf87dbfcf4 to your computer and use it in GitHub Desktop.
Simplest Dropdown Menu in Flutter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| class DropdownButtonRaw<T> extends StatefulWidget { | |
| const DropdownButtonRaw({ | |
| super.key, | |
| required this.items, | |
| required this.value, | |
| required this.onChanged, | |
| }); | |
| final Map<T, Widget> items; | |
| final T value; | |
| final ValueChanged<T> onChanged; | |
| @override | |
| State<DropdownButtonRaw<T>> createState() => _DropdownButtonRawState<T>(); | |
| } | |
| class _DropdownButtonRawState<T> extends State<DropdownButtonRaw<T>> { | |
| final _rootKey = GlobalKey(); | |
| final _controller = OverlayPortalController(); | |
| @override | |
| Widget build(final BuildContext context) { | |
| final items = widget.items; | |
| final item = items[widget.value]; | |
| return InkWell( | |
| key: _rootKey, | |
| onTap: _controller.show, | |
| child: OverlayPortal( | |
| controller: _controller, | |
| overlayChildBuilder: (final _) => _DropdownMenu<T>( | |
| rootKey: _rootKey, | |
| controller: _controller, | |
| children: items.entries | |
| .map( | |
| (final e) => _DropdownMenuItem( | |
| controller: _controller, | |
| item: e, | |
| onChanged: widget.onChanged, | |
| ), | |
| ) | |
| .toList(), | |
| ), | |
| child: item, | |
| ), | |
| ); | |
| } | |
| } | |
| class _DropdownMenu<T> extends StatelessWidget { | |
| const _DropdownMenu({ | |
| required this.rootKey, | |
| required this.controller, | |
| required this.children, | |
| }); | |
| final GlobalKey rootKey; | |
| final OverlayPortalController controller; | |
| final List<_DropdownMenuItem<T>> children; | |
| @override | |
| Widget build(final BuildContext context) => TapRegion( | |
| onTapOutside: (final _) => controller.hide(), | |
| groupId: rootKey, | |
| child: _DropdownMenuLayout(rootKey: rootKey, children: children), | |
| ); | |
| } | |
| class _DropdownMenuItem<T> extends StatelessWidget { | |
| const _DropdownMenuItem({ | |
| super.key, | |
| required this.controller, | |
| required this.item, | |
| required this.onChanged, | |
| }); | |
| final OverlayPortalController controller; | |
| final MapEntry<T, Widget> item; | |
| final ValueChanged<T> onChanged; | |
| @override | |
| Widget build(final BuildContext context) => InkWell( | |
| onTap: () { | |
| onChanged(item.key); | |
| controller.hide(); | |
| }, | |
| child: Align(alignment: Alignment.centerLeft, child: item.value), | |
| ); | |
| } | |
| class _DropdownMenuLayout extends StatelessWidget { | |
| const _DropdownMenuLayout({required this.rootKey, required this.children}); | |
| final GlobalKey rootKey; | |
| final List<Widget> children; | |
| @override | |
| Widget build(final BuildContext context) { | |
| final rootContext = rootKey.currentContext!; | |
| final topLeft = | |
| (rootContext.findRenderObject()! as RenderBox).localToGlobal( | |
| Offset.zero, | |
| ancestor: Overlay.of(rootContext).context.findRenderObject(), | |
| ); | |
| return CustomSingleChildLayout( | |
| delegate: _DropdownMenuPositionDelegate(topLeft: topLeft), | |
| child: IntrinsicWidth( | |
| child: Material( | |
| child: Column(mainAxisSize: MainAxisSize.min, children: children), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class _DropdownMenuPositionDelegate extends SingleChildLayoutDelegate { | |
| const _DropdownMenuPositionDelegate({required this.topLeft}); | |
| final Offset topLeft; | |
| @override | |
| BoxConstraints getConstraintsForChild(final BoxConstraints constraints) => | |
| BoxConstraints( | |
| maxWidth: constraints.maxWidth, | |
| maxHeight: constraints.maxHeight, | |
| ); | |
| @override | |
| Offset getPositionForChild(final Size size, final Size childSize) => topLeft; | |
| @override | |
| bool shouldRelayout(final _DropdownMenuPositionDelegate oldDelegate) => | |
| oldDelegate.topLeft != topLeft; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment