Instantly share code, notes, and snippets.
Created
April 20, 2019 04:53
-
Save diegoveloper/a388dd42a01ffff04cd51ec026381fe3 to your computer and use it in GitHub Desktop.
This file contains 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 MyOwnPopupMenuButton<T> extends StatefulWidget { | |
/// Creates a button that shows a popup menu. | |
/// | |
/// The [itemBuilder] argument must not be null. | |
const MyOwnPopupMenuButton({ | |
Key key, | |
@required this.itemBuilder, | |
this.initialValue, | |
this.onSelected, | |
this.onCanceled, | |
this.tooltip, | |
this.elevation = 8.0, | |
this.enabled = true, | |
this.padding = const EdgeInsets.all(8.0), | |
this.child, | |
this.icon, | |
this.offset = Offset.zero, | |
}) : assert(itemBuilder != null), | |
assert(offset != null), | |
assert(!(child != null && | |
icon != null)), // fails if passed both parameters | |
super(key: key); | |
/// Called when the button is pressed to create the items to show in the menu. | |
final PopupMenuItemBuilder<T> itemBuilder; | |
/// The value of the menu item, if any, that should be highlighted when the menu opens. | |
final T initialValue; | |
/// Called when the user selects a value from the popup menu created by this button. | |
/// | |
/// If the popup menu is dismissed without selecting a value, [onCanceled] is | |
/// called instead. | |
final PopupMenuItemSelected<T> onSelected; | |
/// Called when the user dismisses the popup menu without selecting an item. | |
/// | |
/// If the user selects a value, [onSelected] is called instead. | |
final PopupMenuCanceled onCanceled; | |
/// Text that describes the action that will occur when the button is pressed. | |
/// | |
/// This text is displayed when the user long-presses on the button and is | |
/// used for accessibility. | |
final String tooltip; | |
/// The z-coordinate at which to place the menu when open. This controls the | |
/// size of the shadow below the menu. | |
/// | |
/// Defaults to 8, the appropriate elevation for popup menus. | |
final double elevation; | |
/// Matches IconButton's 8 dps padding by default. In some cases, notably where | |
/// this button appears as the trailing element of a list item, it's useful to be able | |
/// to set the padding to zero. | |
final EdgeInsetsGeometry padding; | |
/// If provided, the widget used for this button. | |
final Widget child; | |
/// If provided, the icon used for this button. | |
final Icon icon; | |
/// The offset applied to the Popup Menu Button. | |
/// | |
/// When not set, the Popup Menu Button will be positioned directly next to | |
/// the button that was used to create it. | |
final Offset offset; | |
final bool enabled; | |
@override | |
_PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>(); | |
} | |
class _PopupMenuButtonState<T> extends State<MyOwnPopupMenuButton<T>> { | |
void showButtonMenu() { | |
final RenderBox button = context.findRenderObject(); | |
final RenderBox overlay = Overlay.of(context).context.findRenderObject(); | |
final RelativeRect position = RelativeRect.fromRect( | |
Rect.fromPoints( | |
button.localToGlobal(widget.offset, ancestor: overlay), | |
button.localToGlobal(button.size.bottomRight(Offset.zero), | |
ancestor: overlay), | |
), | |
Offset.zero & overlay.size, | |
); | |
showMenu<T>( | |
context: context, | |
elevation: widget.elevation, | |
items: widget.itemBuilder(context), | |
initialValue: widget.initialValue, | |
position: position, | |
).then<void>((T newValue) { | |
if (!mounted) return null; | |
if (newValue == null) { | |
if (widget.onCanceled != null) widget.onCanceled(); | |
return null; | |
} | |
if (widget.onSelected != null) widget.onSelected(newValue); | |
}); | |
} | |
Icon _getIcon(TargetPlatform platform) { | |
assert(platform != null); | |
switch (platform) { | |
case TargetPlatform.android: | |
case TargetPlatform.fuchsia: | |
return const Icon(Icons.more_vert); | |
case TargetPlatform.iOS: | |
return const Icon(Icons.more_horiz); | |
} | |
return null; | |
} | |
@override | |
Widget build(BuildContext context) { | |
assert(debugCheckHasMaterialLocalizations(context)); | |
return widget.child != null | |
? InkWell( | |
onTap: widget.enabled ? showButtonMenu : null, | |
child: widget.child, | |
) | |
: IconButton( | |
icon: widget.icon ?? _getIcon(Theme.of(context).platform), | |
padding: widget.padding, | |
tooltip: widget.tooltip ?? | |
MaterialLocalizations.of(context).showMenuTooltip, | |
onPressed: widget.enabled ? showButtonMenu : null, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment