Skip to content

Instantly share code, notes, and snippets.

@ScottS2017
Created July 3, 2018 15:43
Show Gist options
  • Save ScottS2017/de81cfec18950625fd27fe8ac20112f1 to your computer and use it in GitHub Desktop.
Save ScottS2017/de81cfec18950625fd27fe8ac20112f1 to your computer and use it in GitHub Desktop.
simon overlay
import 'package:flutter/widgets.dart';
/// Displays an overlay Widget anchored directly above the center of this
/// [AnchoredOverlay].
///
/// The overlay Widget is created by invoking the provided [overlayBuilder].
///
/// The [anchor] position is provided to the [overlayBuilder], but the builder
/// does not have to respect it. In other words, the [overlayBuilder] can
/// interpret the meaning of "anchor" however it wants - the overlay will not
/// be forced to be centered about the [anchor].
///
/// The overlay built by this [AnchoredOverlay] can be conditionally shown
/// and hidden by settings the [showOverlay] property to true or false.
///
/// The [overlayBuilder] is invoked every time this Widget is rebuilt.
class AnchoredOverlay extends StatelessWidget {
final bool showOverlay;
final Widget Function(BuildContext, Rect anchorBounds, Offset anchor) overlayBuilder;
final Widget child;
AnchoredOverlay({
key,
this.showOverlay = false,
this.overlayBuilder,
this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Container(
// This LayoutBuilder gives us the opportunity to measure the above
// Container to calculate the "anchor" point at its center.
child: new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return new OverlayBuilder(
showOverlay: showOverlay,
overlayBuilder: (BuildContext overlayContext) {
// To calculate the "anchor" point we grab the render box of
// our parent Container and then we find the center of that box.
RenderBox box = context.findRenderObject() as RenderBox;
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
final bottomRight = box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
final Rect anchorBounds = new Rect.fromLTRB(
topLeft.dx,
topLeft.dy,
bottomRight.dx,
bottomRight.dy,
);
final anchorCenter = box.size.center(topLeft);
return overlayBuilder(overlayContext, anchorBounds, anchorCenter);
},
child: child,
);
},
),
);
}
}
/// Displays an overlay Widget as constructed by the given [overlayBuilder].
///
/// The overlay built by the [overlayBuilder] can be conditionally shown
/// and hidden by settings the [showOverlay] property to true or false.
///
/// The [overlayBuilder] is invoked every time this Widget is rebuilt.
///
/// Implementation note: the reason we rebuild the overlay every time our
/// state changes is because there doesn't seem to be any better way to
/// invalidate the overlay itself than to invalidate this Widget. Remember,
/// overlay Widgets exist in [OverlayEntry]s which are inaccessible to
/// outside Widgets. But if a better approach is found then feel free to use it.
class OverlayBuilder extends StatefulWidget {
final bool showOverlay;
final Widget Function(BuildContext) overlayBuilder;
final Widget child;
OverlayBuilder({
key,
this.showOverlay = false,
this.overlayBuilder,
this.child,
}) : super(key: key);
@override
_OverlayBuilderState createState() => new _OverlayBuilderState();
}
class _OverlayBuilderState extends State<OverlayBuilder> {
OverlayEntry overlayEntry;
@override
void initState() {
super.initState();
if (widget.showOverlay) {
WidgetsBinding.instance.addPostFrameCallback((_) => showOverlay());
}
}
@override
void didUpdateWidget(OverlayBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
}
@override
void reassemble() {
super.reassemble();
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
}
@override
void dispose() {
if (isShowingOverlay()) {
hideOverlay();
}
super.dispose();
}
bool isShowingOverlay() => overlayEntry != null;
void showOverlay() {
if (overlayEntry == null) {
// Create the overlay.
overlayEntry = new OverlayEntry(
builder: widget.overlayBuilder,
);
addToOverlay(overlayEntry);
} else {
// Rebuild overlay.
buildOverlay();
}
}
void addToOverlay(OverlayEntry entry) async {
Overlay.of(context).insert(entry);
}
void hideOverlay() {
if (overlayEntry != null) {
overlayEntry.remove();
overlayEntry = null;
}
}
void syncWidgetAndOverlay() {
if (isShowingOverlay() && !widget.showOverlay) {
hideOverlay();
} else if (!isShowingOverlay() && widget.showOverlay) {
showOverlay();
}
}
void buildOverlay() async {
overlayEntry?.markNeedsBuild();
}
@override
Widget build(BuildContext context) {
buildOverlay();
return widget.child;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment