One thing that has always felt a little limiting in Flutter for us has been its inability to perform hit-testing for a button or gesture detector that is outside the parents bounds. This has been a popular issue in the Flutter bug-base over the years, getting something around 150+ upvotes if you add up all incarnations of the issue.
The concept of “negative margins” is common in most rich interfaces stacks, like html, unity, flash etc, and is useful for a wide variety of simple techniques and tricks. Some of the more common use cases are drop-down menus, sliding panels, “hanging” ui elements or just general layout tweaks. For years these techniques have been unusable in Flutter because anything you put in negative margins, will no longer accept gestures.
To be fair, Flutter does provide the Overlay
widget, to ostensibly handle these use cases. The problem is that it has an API surface that is a bit awkward to use and gets quite complicated when it comes to CompositedTransformFollower and CompositedTransformTarget classes. It’s also fairly inflexible as contents of Overlay always render on top of the entire page route, which is not always desired.
From our experience Overlay is well suited to full-screen overlays that exist in a fixed position, but not really ideal for these inline popups that need to be attached or aligned to some widget within the tree. We find these use cases are generally far simpler to implement using simple offsets.
Finally with the `defer_pointer
package we can make it happen:
https://pub.dev/packages/defer_pointer
What is it?
(almost entirely) Based on an example posted by github user @shrouxm, defer_pointer
provides an alternative to Overlay
which allows you to easily render and hit test a widget outside its parent bounds.
The core idea is pretty simple: If we can’t do the hit test ourselves, then just punt it to another widget further down the tree!
Usage
- Wrap a
DeferredPointerHandler
somewhere above the buttons that you wish to hit-test. - Wrap a
DeferPointer
widget around the buttons themselves.
Widget build(BuildContext context) { return DeferredPointerHandler( child: SizedBox( width: 100, height: 100, child: Stack(clipBehavior: Clip.none, children: [ // Hang button off the bottom of the content Positioned( bottom: -30, child: DeferPointer(child: _SomeBtn(false)), ), // Content Positioned.fill(child: SomeContent()), ])))); }
In the above example the DeferredPointerHandler
will handle all hit testing for any DeferPointer.child
widgets, enabling them to escape their parents bounds.
You can allow the child to be painted normally, or use the paintOnTop
parameter to move the paint up the tree into the DeferredPointerHandler
:
return DeferPointer( paintOnTop: true, child: TextButton(...));
This is useful for dropdowns or panels where you always want the content to render above everything around it, while still retaining the flexibility to render in place in cases where the design calls for it.
Examples
The package provides 4 usage examples:
1. An auto-complete search field:
2. A simple example of offsetting 2 buttons outside their stack:
3. A classic desktop/web style dropdown menu:
4. A animated menu based on the Flow
widget:
For more details check out the full README if you find any issues, please let us know here.
Hopefully this can free up your layouts and bit and help you, literally, break outside that box 😀
For a long time, I have been trying to find a solution to this problem, and unfortunately all the answers directed me to use the overlay!
I had started using OverflowBox in one of my projects and then I was surprised that the card doesn’t respond to the click!
After a lot of searching, I had to give up OverflowBox!
Many thanks for this plugin ❤
Many Thanks too !!!
Thank you very much.
I am trying to make a sliding menu, but the biggest issue I’m having is being able to translate a GestureDetector widget halfway out of the parent. This plugin is working perfectly for my use case, great job Shawn & Shrouxm.