-
-
Save talamaska/b7e6d39150cdaa43e09b3348591e185b to your computer and use it in GitHub Desktop.
A useful OverlayPortal widget wrapper for building based on the anchor/target's position.
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/material.dart' show Card; | |
import 'package:flutter/widgets.dart'; | |
class PositionedOverlayBuilder extends StatefulWidget { | |
const PositionedOverlayBuilder({ | |
required this.anchorBuilder, | |
required this.overlayChildBuilder, | |
this.onHide, | |
this.overlayConstraints, | |
this.dismissible = true, | |
this.barrierColor, | |
this.debugLabel, | |
super.key, | |
}); | |
final String? debugLabel; | |
final Color? barrierColor; | |
final bool dismissible; | |
final BoxConstraints? overlayConstraints; | |
final void Function()? onHide; | |
final Widget Function( | |
BuildContext context, | |
OverlayPortalController controller, | |
) anchorBuilder; | |
final Widget Function( | |
BuildContext context, | |
OverlayPortalController controller, | |
) overlayChildBuilder; | |
@override | |
State<PositionedOverlayBuilder> createState() => | |
_PositionedOverlayBuilderState(); | |
} | |
class _PositionedOverlayBuilderState extends State<PositionedOverlayBuilder> | |
with WidgetsBindingObserver { | |
late final controller = | |
OverlayPortalController(debugLabel: widget.debugLabel); | |
late final key = GlobalKey(debugLabel: widget.debugLabel); | |
Offset widgetOrigin(RenderBox renderBox) { | |
return renderBox.localToGlobal(Offset.zero); | |
} | |
RenderBox getRenderBox() => | |
key.currentContext!.findRenderObject()! as RenderBox; | |
@override | |
void initState() { | |
super.initState(); | |
WidgetsBinding.instance.addObserver(this); | |
} | |
@override | |
void dispose() { | |
WidgetsBinding.instance.removeObserver(this); | |
super.dispose(); | |
} | |
late Size _lastSize = | |
View.of(context).physicalSize / View.of(context).devicePixelRatio; | |
@override | |
void didChangeMetrics() { | |
if (!controller.isShowing) return; | |
final view = View.of(context); | |
final size = view.physicalSize / view.devicePixelRatio; | |
if (_lastSize == size) return; | |
setState(() => _lastSize = size); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return OverlayPortal.targetsRootOverlay( | |
key: key, | |
controller: controller, | |
overlayChildBuilder: (context) { | |
final renderBox = getRenderBox(); | |
final origin = widgetOrigin(renderBox); | |
final isTop = _lastSize.height / 2 > origin.dy; | |
final isLeft = _lastSize.width / 2 > origin.dx; | |
final position = switch ((isTop, isLeft)) { | |
(true, true) => renderBox.size.bottomLeft(origin), | |
(true, false) => renderBox.size.bottomRight(origin), | |
(false, true) => renderBox.size.topLeft(origin), | |
(false, false) => renderBox.size.topRight(origin), | |
}; | |
final left = isLeft ? position.dx : null; | |
final right = isLeft ? null : _lastSize.width - position.dx; | |
final top = isTop ? position.dy : null; | |
final bottom = isTop ? null : _lastSize.height - origin.dy; | |
final constraints = widget.overlayConstraints; | |
late final child = Card( | |
clipBehavior: Clip.hardEdge, | |
elevation: 20, | |
child: widget.overlayChildBuilder( | |
context, | |
controller, | |
), | |
); | |
return Stack( | |
children: [ | |
if (widget.dismissible) | |
Positioned.fill( | |
child: GestureDetector( | |
behavior: HitTestBehavior.translucent, | |
onTap: () { | |
widget.onHide?.call(); | |
controller.hide(); | |
}, | |
), | |
), | |
Positioned( | |
left: left, | |
right: right, | |
top: top, | |
bottom: bottom, | |
child: constraints == null | |
? child | |
: ConstrainedBox(constraints: constraints, child: child), | |
), | |
], | |
); | |
}, | |
child: widget.anchorBuilder( | |
context, | |
controller, | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment