PortalEntry class
A widget that renders its content in a different location of the widget tree.
In short, you can use PortalEntry to show dialogs, tooltips, contextual menus, ...
You can then control the visibility of these overlays with a simple setState
.
The benefits of using PortalEntry over Overlay/OverlayEntry are multiple:
- PortalEntry is easier to manipulate
- It allows aligning your menus/tooltips next to a button easily
- It combines nicely with state-management solutions and the "state-restoration" framework. For example, combined with RestorableProperty, when the application is killed then re-opened, modals/menus would be restored.
For PortalEntry to work, make sure to insert Portal higher in the widget-tree.
Contextual menu example
In this example, we will see how we can use PortalEntry to show a menu after clicking on a ElevatedButton.
First, we need to create a StatefulWidget that renders our ElevatedButton:
class MenuExample extends StatefulWidget {
@override
_MenuExampleState createState() => _MenuExampleState();
}
class _MenuExampleState extends State<MenuExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {},
child: Text('show menu'),
),
),
);
}
}
Then, we need to insert our PortalEntry in the widget tree.
We want our contextual menu to render right next to our ElevatedButton. As such, our PortalEntry should be the parent of ElevatedButton like so:
Center(
child: PortalEntry(
visible: // <todo>
portal: // <todo>
child: ElevatedButton(
...
),
),
)
We can pass our menu to PortalEntry:
PortalEntry(
visible: true,
portal: Material(
elevation: 8,
child: IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(title: Text('option 1')),
ListTile(title: Text('option 2')),
],
),
),
),
child: RaiseButton(...),
)
At this stage, you may notice two things:
- our menu is full-screen
- our menu is always visible (because
visible
is true)
Let's fix the full-screen issue first and change our code so that our menu renders on the right of our ElevatedButton.
To align our menu around our button, we can specify the childAnchor
and
portalAnchor
parameters:
PortalEntry(
visible: true,
portalAnchor: Alignment.topLeft,
childAnchor: Alignment.topRight,
portal: Material(...),
child: RaiseButton(...),
)
What this code means is, this will align the top-left of our menu with the top-right or the ElevatedButton. With this, our menu is no-longer full-screen and is now located to the right of our button.
Finally, we can update our code such that the menu show only when clicking on the button.
To do that, we need to declare a new boolean inside our StatefulWidget, that says whether the menu is open or not:
class _MenuExampleState extends State<MenuExample> {
bool isMenuOpen = false;
...
}
We then pass this isMenuOpen
variable to our PortalEntry:
PortalEntry(
visible: isMenuOpen,
...
)
Then, inside the onPressed
callback of our ElevatedButton, we can
update this isMenuOpen
variable:
ElevatedButton(
onPressed: () {
setState(() {
isMenuOpen = true;
});
},
child: Text('show menu'),
),
One final step is to close the menu when the user clicks randomly outside of the menu.
This can be implemented with a second PortalEntry combined with GestureDetector like so:
Center(
child: PortalEntry(
visible: isMenuOpen,
portal: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
isMenuOpen = false;
});
},
),
child: PortalEntry(
// our previous PortalEntry
portal: Material(...)
child: ElevatedButton(...),
),
),
)
- Inheritance
-
- Object
- DiagnosticableTree
- Widget
- StatefulWidget
- PortalEntry
Constructors
Properties
- child → Widget
-
final
- childAnchor → Alignment?
-
final
- closeDuration → Duration?
-
final
- hashCode → int
-
The hash code for this object.
no setterinherited
- key → Key?
-
Controls how one widget replaces another widget in the tree.
finalinherited
- portal → Widget?
-
final
- portalAnchor → Alignment?
-
final
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- visible → bool
-
final
Methods
-
createElement(
) → StatefulElement -
Creates a StatefulElement to manage this widget's location in the tree.
inherited
-
createState(
) → _PortalEntryState -
Creates the mutable state for this widget at a given location in the tree.
override
-
debugDescribeChildren(
) → List< DiagnosticsNode> -
Returns a list of
DiagnosticsNode
objects describing this node's children.inherited -
debugFillProperties(
DiagnosticPropertiesBuilder properties) → void -
Add additional properties associated with the node.
override
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
toDiagnosticsNode(
{String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode -
Returns a debug representation of the object that is used by debugging
tools and by DiagnosticsNode.toStringDeep.
inherited
-
toString(
{DiagnosticLevel minLevel = DiagnosticLevel.info}) → String -
A string representation of this object.
inherited
-
toStringDeep(
{String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String -
Returns a string representation of this node and its descendants.
inherited
-
toStringShallow(
{String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String -
Returns a one-line detailed description of the object.
inherited
-
toStringShort(
) → String -
A short, textual description of this widget.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited