Created
July 3, 2018 15:43
-
-
Save ScottS2017/de81cfec18950625fd27fe8ac20112f1 to your computer and use it in GitHub Desktop.
simon overlay
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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