I'm trying to give a widget far down in the widget tree absolute positions. Because it is somewhere down in the widget tree, it is most likely that there will be certain layout constraints set by its ancestors. I'm looking for something that can ignore those constraints and just use absolute coordinates.
If the explanation is confusing, I drew a little scheme of something that represents my wanted outcome:
I tried doing this using a Stack
, but that makes it difficult to pass in absolute coordinates, even with a Positioned
because setting the property left: 0.0
would use relative coordinates to the ancestor. So setting it to zero would not necessarily mean that the widget would be positioned at the top left of the screen.
I also tried using an Overlay
but I the results are pretty much the same as with the Stack
implementation.
What is the recommended way of doing this in Flutter?
I'll describe two ways to do this, but I won't provide the code or an example as I don't think putting this out there in a copy-paste-able way is necessarily a good idea - I agree with @Remi's comment that if you need to do this in an app, there's a good chance that the app could be refactored to avoid it. However, there is a way to do this - although be warned, it might cause some problems with things such as touch events (although I think there's ways to get around that - see AbsorbPointer and IgnorePointer for starting points). Since you haven't really explained what you want this for, I'm going to assume that isn't something you need.
I would encourage you to look into other ways of doing whatever you're trying to do and ask if you'd like help figuring out that better way.
Anyways, on to the good stuff =P :
Method # 1 - using an existing overlay:
I have actually used this method myself for a modal loading indicator which I want to persist while between pages underneath are being pushed and which can be called from anywhere within an app without requiring any widgets be in the tree above it. That's a very specific use-case though which I think justifies this usage.
Basically, you want to use an Overlay, but instead of creating your own (which will have the same size as the widget you're dealing with), you want to use an existing one. If you're using a MaterialApp, WidgetApp, or even just a Navigator somewhere in your app, you have an Overlay already which is almost for sure the same size as the screen. If you have multiple layers of overlays, you want to get the one at the top as that's the most likely to cover the entire screen.
To get access to it, you can use OverlayState rootOverlay = context.rootAncestorStateOfType(const TypeMatcher<OverlayState>());
to get ahold of the root overlay (you'll probably want to assert that it actually found one).
Once you have access to it, you can make an OverlayEntry which builds whatever your widget is, and call rootOverlay.insert(...)
to add it. The item built by your OverlayEntry will be positioned from the top left and to the extent of the screen so long as the overlay itself covers the entire screen (you can do the offset yourself). You'll also want to make sure you rootOverlay.remove(...)
at some point, which means keeping a reference to the OverlayEntry. I'd personally create a class called OverlayInsertionManager or something which keeps track of the overlay entry and does insertion/removal.
Method #2 - using your own widget
I would consider this way of doing it slightly cleaner if you're just doing it within your own app, although still probably not a great idea.
Basically, what you want to do is create a Stateful Widget high up in your app - above anything that would take up space on the screen. This could theoretically mean above your MaterialApp/WidgetApp/etc although that might cause problems for you if you're using theme/text directionality/etc that WidgetApp provides. I think you can use the WidgetApp.builder to place your widget where you need it. Let's call it PageOverlay
for convenience (with a corresponding PageOverlayState
.
In your widget you'll have a static of
method like the following (these are littered throughout flutter's source code and are more or less a convention):
static PageOverlayState of(BuildContext context) {
final PageOverlayState result = context.ancestorStateOfType(const TypeMatcher<PageOverlayState>());
assert(() {
if (result == null) {
throw new FlutterError(
'No Overlay widget found.\n'
);
}
return true;
}());
return result;
}
Within your PageOverlayState, you're going to have a variable something like WidgetBuilder _overlayBuidler
which is normally null, and a method/setter something like set overlayBuilder(WidgetBuilder overlayBuilder) => setState(() => _overlayBuilder = overlayBuilder);
In the PageOverlayState's builder, you'll create a Stack; the first child will be the child you get passed into the WidgetApp.builder
or MaterialApp.builder
. You'll only create a second child if _overlayBuilder is not null; if it is not null the second child should be something like new Builder(builder: _overlayBuilder)
.
You might have to do something around sizing the stack properly (i.e. put it in an Expanded or something).