Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import 'package:flutter/material.dart';
class OverlayContainer extends StatefulWidget {
/// The child to render in the regular document flow (defaults to Container())
final Widget child;
/// The widget to render inside the [OverlayEntry].
final Widget overlay;
/// Offset to apply to the [CompositedTransformFollower]
final Offset offset;
/// Controlling whether the overlay is current showing or not.
///
/// Note: if you have an animated component you'll probably need to leave this `true`
/// and wrap your floating widget in a `Visible`:
/// ```dart
/// Visibility(
/// visible: isVisible,
/// maintainSize: true,
/// maintainState: true,
/// maintainAnimation: true,
/// // ...
/// ```
final bool show;
OverlayContainer({
Key key,
@required this.overlay,
this.child,
this.offset,
this.show = true,
}) : super(key: key);
@override
_OverlayContainerState createState() => _OverlayContainerState();
}
class _OverlayContainerState extends State<OverlayContainer>
with WidgetsBindingObserver {
OverlayEntry _overlayEntry;
final LayerLink _layerLink = LayerLink();
@override
void initState() {
super.initState();
if (widget.show) {
_show();
}
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeMetrics() {
// We would want to re render the overlay if any metrics
// ever change.
if (widget.show) {
_show();
} else {
_hide();
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// We would want to re render the overlay if any of the dependencies
// ever change.
if (widget.show) {
_show();
} else {
_hide();
}
}
@override
void didUpdateWidget(OverlayContainer oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.show) {
_show();
} else {
_hide();
}
}
@override
void dispose() {
if (widget.show) {
_hide();
}
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void _show() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (_overlayEntry == null) {
_overlayEntry = _buildOverlayEntry();
Overlay.of(context).insert(_overlayEntry);
} else {
_overlayEntry.markNeedsBuild();
}
});
}
void _hide() {
if (_overlayEntry != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_overlayEntry.remove();
_overlayEntry = null;
});
}
}
@override
Widget build(BuildContext context) {
// Listen to changes in media query such as when a device orientation changes
// or when the keyboard is toggled.
// MediaQuery.of(context);
return CompositedTransformTarget(
link: this._layerLink,
child: widget.child ?? Container(),
);
}
OverlayEntry _buildOverlayEntry() {
return OverlayEntry(
opaque: false,
builder: (context) {
return FittedBox(
fit: BoxFit.cover,
child: CompositedTransformFollower(
offset: widget.offset,
link: this._layerLink,
//showWhenUnlinked: false,
child: FittedBox(
fit: BoxFit.none,
child: widget.overlay,
),
),
);
},
);
}
}
@micimize
Copy link
Author

micimize commented May 4, 2020

Notes for myself (or anyone) wanting to tackle the anchored overlay problem more seriously:

  • investigate the translation approach, especially whether the flutter team would sanction it
    (see flutter/flutter#19445 on Stack overflow gesture detection)
  • There is a high (presumed) cost to rendering unnecessary OverlayEntrys
  • Thus, "True" animated entry ends up being an implicit subproblem here to avoid rendering empty ones
  • some useful discussion in MustansirZia/overlay_container#6

@micimize
Copy link
Author

micimize commented May 4, 2020

Usage dartpad. It's gist has some troubleshooting discussion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment