This tutorial shows you how to use the OverlayPortal
widget in Flutter.
Flutter allows us to float widgets on top of other widgets by using Overlay
. An Overlay
itself can be shown or hidden conditionally. If you want to easily show or hide a widget on an Overlay
, you can use the OverlayPortal
widget, which is used to render its overlay child on an Overlay
. It takes a controller that can be used to programmatically manage the visibility of the Overlay
. Below are the usage examples.
Using OverlayPortal
To use the OverlayPortal
widget, you need to call the constructor.
OverlayPortal({
Key? key,
required OverlayPortalController controller,
required Widget Function(BuildContext) overlayChildBuilder,
Widget? child,
})
There are two required arguments. The first one is controller
whose type is OverlayPortalController
. You need to pass an instance of OverlayPortalController
. It allows you to programmatically show or hide the overlay as well as getting whether it's currently shown.
The other required argument is overlayChildBuidler
. You have to pass a function which has a parameter of type BuildContext
. The function needs to return a widget to be displayed as the overlay. You can return any widget. The main challenge is how to render the widget on the desired position which is going to be explained later.
Beside those two required arguments, you can pass a Widget
as the child
argument which will be set as the child of the OverlayPortal
's parent. You can also pass the Key
just like most other Flutter widgets.
First, you have to create an instance of the controller. It can be done by calling the constructor. You can store the instance in a state variable.
final OverlayPortalController _overlayPortalController = OverlayPortalController();
The controller also has an optional argument that allows you to pass a string value as the debugLabel
argument.
final OverlayPortalController _overlayPortalController = OverlayPortalController(
debugLabel: 'My Controller',
);
Then, create the OverlayPortal
widget and pass the controller instance as the controller
argument. We also have to pass a builder function that returns the overlay widget as the overlayChildBuilder
argument.
In the example below, we create a button that can be used to toggle the visibility of the overlay. To check whether the overlay is currently visible, access the isShowing
property of the controller. The controller has show()
and hide()
methods that can be used to control whether the overlay should be visible. We also pass a child
argument to the OverlayPortal
widget to show a text on the button.
ElevatedButton(
onPressed: () {
_overlayPortalController.isShowing
? _overlayPortalController.hide()
: _overlayPortalController.show();
},
child: OverlayPortal(
controller: _overlayPortalController,
overlayChildBuilder: (BuildContext context) {
return const Text('This is overlay', style: TextStyle(color: Colors.black));
},
child: const Text('Show/Hide Overlay'),
),
)
Below is the output:
In the code above, we don't explicitly specify where to place the overlay widget. As you can see from the output, when visible, it's rendered at the top of the screen.
Adjust Overlay Widget Position
Unfortunately, Flutter doesn't automatically position the overlay widget according to the position of the parent widget. As a result, it may be rendered far from its parent. If you need to place the overlay widget relative to its parent, you may need to get the parent's position first. You can read our tutorial about how to get the size and position of a widget in Flutter.
Basically, you need to create a GlobalKey
and set it as the key of the parent widget. Then, get the RenderBox
, which allows you to get the offset of the parent widget.
final GlobalKey _buttonKey = GlobalKey();
Offset _getButtonOffset() {
final RenderBox renderBox = _buttonKey.currentContext?.findRenderObject() as RenderBox;
return renderBox.localToGlobal(Offset.zero);
}
Below is the updated example where the offset of the overlay widget is set using a Positioned
widget based on the offset of the parent widget.
ElevatedButton(
key: _buttonKey,
onPressed: () {
_overlayPortalController.isShowing
? _overlayPortalController.hide()
: _overlayPortalController.show();
},
child: OverlayPortal(
controller: _overlayPortalController,
overlayChildBuilder: (BuildContext context) {
// return const Text('This is overlay', style: TextStyle(color: Colors.black));
return Positioned(
top: _getButtonOffset().dy + 60,
right: _getButtonOffset().dx,
child: Container(
// color: Colors.teal,
decoration: BoxDecoration(
color: Colors.teal,
borderRadius: BorderRadius.circular(15.0),
),
child: const Padding(
padding: EdgeInsets.all(5.0),
child: Text('This is overlay', style: TextStyle(color: Colors.white)),
),
),
);
},
child: const Text('Show/Hide Overlay'),
),
)
Output:
OverlayPortal
Parameters
Key? key
: The widget's key, used to control how a widget is replaced with another.required OverlayPortalController controller
: The controller that can be used to control the overlay widget.required Widget Function(BuildContext) overlayChildBuilder
: A function that returns a widget to be displayed as the overlay.Widget? child
: A widget that will be set as the child of theOverlayPortal
's parent.
Summary
The OverlayPortal
widget is suitable if you need to display an Overlay
that can be shown or hidden. It depends on an OverlayPortalController
which can be used to set the visibility of the Overlay
. The widget that will be shown on the Overlay
is built using a function passed as the overlayChildBuilder
. You can also pass a widget as the child
argument to be set as the child of the OverlayPortal
's parent widget