Reputation: 4129
I need to get the PopupMenuButton
's size because it has a property offset
which controls where the dropdown menu is rendered on the screen, and I want this one to be rendered such that the top left of the drop down menu is aligned with the bottom left of the PopupMenuButton
(see image below).
My approach now is this:
extension TextExtension on Text {
/// Calculates the size of the text inside this text widget.
/// note: this method supposes ltr direction of text, which is not always true, but it doesn't affect the size that much, so
/// keep in mind that the size returned may be approximate in some cases.
/// The text inside this widget must be non-null before calling this method.
Size getSize({TextDirection? textDirection}) {
TextPainter tp = TextPainter(
text: TextSpan(text: data),
textDirection: textDirection ?? TextDirection.ltr)
..layout();
return tp.size;
}
}
And then when I define the PopupMenuButton
, I do this:
Widget _dropDownMenu({
required BuildContext context,
required String title,
required List<PopupMenuItem> items,
}) {
final text = Text(
title,
style: Theme.of(context).textTheme.bodyMedium,
);
final textSize = text.size;
return PopupMenuButton(
child: text,
itemBuilder: (context) => items,
offset: Offset(0, textSize.height),
);
}
It works, but I don't like it. I think there must be a better way to do this.
This is how it looks like right now:
I tried LayoutBuilder
, but it is returning infinite width constraints.
Is there a more clean way of doing this?
Upvotes: 0
Views: 730
Reputation: 4129
It seems there is no other way except the approach I mentioned in the question, or to modify the source code of the PopupMenuButton
to make it accept an OffsetBuilder
as pskink mentioned. This can be done like this (and there is full working example below):
PopupMenuButton
source code and copy it all into a new file custom_popup_menu.dart
(in this new file just remove all the imports and import them again as suggested by the IDE to fix them)Offset _defaultOffsetBuilder(Size size) => Offset.zero;
PopupMenuButton
class replace final Offset offset
with /// The button size will be passed to this function to get the offset applied
/// to the Popup Menu when it is open. The top left of the [PopupMenuButton] is considered
/// as the origin of the coordinate system of this offset.
///
/// When not set, the Popup Menu Button will be positioned directly next to
/// the button that was used to create it.
final Offset Function(Size) offsetBuilder;
inside the constructor of this class replace this.offset
with this.offsetBuilder = _defaultOffsetBuilder,
in the showButtonMenu
method of PopupMenuButtonState
class, replace
Rect.fromPoints(
button.localToGlobal(widget.offset, ancestor: overlay),
button.localToGlobal(
button.size.bottomRight(Offset.zero) + widget.offset,
ancestor: overlay),
),
Offset.zero & overlay.size,
);
with
final offset = widget.offsetBuilder(button.size);
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(offset, ancestor: overlay),
button.localToGlobal(
button.size.bottomRight(Offset.zero) + offset,
ancestor: overlay),
),
Offset.zero & overlay.size,
);
... (imports)
import 'custom_popup_menu.dart' as pm;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage2(),
);
}
class HomePage2 extends StatelessWidget {
const HomePage2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
body: Align(
alignment: const Alignment(0, -0.8),
child: Container(
decoration: BoxDecoration(border: Border.all(width: 2.0)),
child: pm.PopupMenuButton<String>(
child: const Text(
'Press Me',
style: TextStyle(color: Colors.black, fontSize: 50),
),
itemBuilder: (context) => [
_buildPopupMenuItem(),
_buildPopupMenuItem(),
_buildPopupMenuItem(),
],
color: Colors.red,
offsetBuilder: (buttonSize) => Offset(0, buttonSize.height),
),
),
),
);
pm.PopupMenuItem<String> _buildPopupMenuItem() {
return pm.PopupMenuItem(
child: Text(
'Press Me ${Random().nextInt(100)}',
style: const TextStyle(color: Colors.black, fontSize: 50),
),
onTap: () {},
);
}
}
Upvotes: 3