项目中需要自定义PopupMenuButton
,PopupMenuButton
只提供定制其中的Item
,所以需要看源码一步步看是怎么实现的。
一个核心方法
void showButtonMenu() {
// 通过findRenderObject获取PopupMenuButton的尺寸(button.size)
final RenderBox button = context.findRenderObject();
// 通过findRenderObject获取屏幕的尺寸(overlay.size)
//Overlay 是个什么东西呢?
//看下介绍 A [Stack] of entries that can be managed independently
// 一个Stack 可以管理独立的entries。
// 接着看Overlay.of(context)
// 方法介绍 The state from the closest instance of this class that encloses the given context
// 获取包含这个context最近的OverlayState (每一个页面也是Overlay的entry)
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
// RelativeRect也点进去看下介绍
// An immutable 2D, axis-aligned, floating-point rectangle whose coordinates are given relative to another rectangle's edges, known as the container
// 一个相对的2D矩形 尺寸相对于父容器
// fromReact肯定是创建矩形,怎么创建?由一个父矩形 和 自己,
从而计算出来了在屏幕的绝对位置。
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(widget.offset, ancestor: overlay),
button.localToGlobal(button.size.bottomRight(Offset.zero),
ancestor: overlay),
),
Offset.zero & overlay.size,
);
// showMenu是核心方法
showMenu<T>(
context: context,
elevation: widget.elevation,
items: widget.itemBuilder(context),
initialValue: widget.initialValue,
position: position,
).then<void>((T newValue) {
if (!mounted) return null;
if (newValue == null) {
if (widget.onCanceled != null) widget.onCanceled();
return null;
}
if (widget.onSelected != null) widget.onSelected(newValue);
});
}
showMenu源码
Future<T> showMenu<T>({
@required BuildContext context,
RelativeRect position,
@required List<PopupMenuEntry<T>> items,
T initialValue,
double elevation = 8.0,
String semanticLabel,
}) {
assert(context != null);
assert(items != null && items.isNotEmpty);
assert(debugCheckHasMaterialLocalizations(context));
String label = semanticLabel;
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
label = semanticLabel;
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
label =
semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
}
// 返回的是Navigator.push 这边可以理解为一个透明背景的page,接下去就是走正常的布局显示。
return Navigator.push(
context,
_CustomPopupMenuRoute<T>(
position: position,
items: items,
initialValue: initialValue,
elevation: elevation,
semanticLabel: label,
theme: Theme.of(context, shadowThemeOnly: true),
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
));
}
CustomPopupMenuRoute-CustomSingleChildLayout- _PopupMenu
_PopupMenu
是最后显示在屏幕上的东西,需要自定义的话,需要单独取出来修改相关布局,这样就可以自定义自己需要的Menu了,还有一种思路就是直接使用Overlay 进行添加浮动的布局,位置需要自己提前获取到。