Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Last active December 11, 2023 13:57
Show Gist options
  • Save hawkkiller/9385a561eecab9acd12bcd64e0d42e55 to your computer and use it in GitHub Desktop.
Save hawkkiller/9385a561eecab9acd12bcd64e0d42e55 to your computer and use it in GitHub Desktop.
A widget that shows a popup relative to a target widget.
import 'package:flutter/material.dart';
/// A widget that shows a popup relative to a target widget.
///
/// The popup is declaratively shown/hidden using an [OverlayPortalController].
///
/// It is positioned relative to the target widget using the [followerAnchor] and [targetAnchor] properties.
class Popup extends StatefulWidget {
const Popup({
required this.child,
required this.follower,
required this.controller,
this.offset = Offset.zero,
this.followerAnchor = Alignment.topCenter,
this.targetAnchor = Alignment.bottomCenter,
super.key,
});
/// The target widget that will be used to position the follower widget.
final Widget child;
/// The widget that will be positioned relative to the target widget.
final Widget follower;
/// The controller that will be used to show/hide the overlay.
final OverlayPortalController controller;
/// The alignment of the follower widget relative to the target widget.
///
/// Defaults to [Alignment.topCenter].
final Alignment followerAnchor;
/// The alignment of the target widget relative to the follower widget.
///
/// Defaults to [Alignment.bottomCenter].
final Alignment targetAnchor;
/// The offset of the follower widget relative to the target widget.
/// This is useful for fine-tuning the position of the follower widget.
///
/// Defaults to [Offset.zero].
final Offset offset;
@override
State<Popup> createState() => _PopupState();
}
class _PopupState extends State<Popup> {
/// The link between the target widget and the follower widget.
final _layerLink = LayerLink();
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
// Link the target widget to the follower widget.
link: _layerLink,
child: OverlayPortal(
controller: widget.controller,
child: widget.child,
overlayChildBuilder: (BuildContext context) {
// It is needed to wrap the follower widget in a widget that fills the space of the overlay.
// This is needed to make sure that the follower widget is positioned relative to the target widget.
// If not wrapped, the follower widget will fill the screen and be positioned incorrectly.
return Align(
child: CompositedTransformFollower(
// Link the follower widget to the target widget.
link: _layerLink,
// The follower widget should not be shown when the link is broken.
showWhenUnlinked: false,
followerAnchor: widget.followerAnchor,
targetAnchor: widget.targetAnchor,
offset: widget.offset,
child: widget.follower,
),
);
},
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment