Flutter-PopupMenuButton是如何实现的

项目中需要自定义PopupMenuButtonPopupMenuButton只提供定制其中的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 进行添加浮动的布局,位置需要自己提前获取到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。