• [Flutter] 扩展一个支持弹出菜单的IconButton


    Flutter有很多的基础Widget,其中IconButton很常用,还有 PopupButton, 这里扩展的这个 AppBarButton 是将两者融合一起,用起来更方便了。

    import 'package:flutter/material.dart';
    
    class AppBarButton<T> extends StatelessWidget {
      final Widget child;
      final Color color, focusColor;
      final double iconSize;
      final String tooltip;
      final bool autofocus;
      /// 菜单列表
      ///
      /// [[{'title': '分享', 'icon': FIcons.share_2, 'type': SHARE}]]
      final List<Map<String, dynamic>> menus;
      final Offset menuOffset;
      final MenusWidgetBuilder<T> menuItemBuilder;
      final T menuInitialValue;
      final T menuSelected;
      final double minWidth, splashRadius;
      final EdgeInsetsGeometry padding;
      final VoidCallback onPressed;
      final ValueChanged<T> onSelected;
    
      const AppBarButton({Key key, Widget child, Widget icon, this.color,
        this.focusColor, this.iconSize, this.tooltip,
        this.minWidth, this.splashRadius,
        this.autofocus,
        this.menus,
        this.menuOffset,
        this.menuItemBuilder,
        this.menuInitialValue,
        this.menuSelected,
        this.onSelected,
        this.padding, VoidCallback onPressed, VoidCallback onTap}):
          child = child ?? icon,
          onPressed = onPressed ?? onTap,
          super(key: key);
    
      @override
      Widget build(BuildContext context) {
        if ((menus != null && menus.isNotEmpty) || menuItemBuilder != null)
          return _buildButton(context, () {
            showButtonMenu(context,
              menus: menus,
              menuItemBuilder: menuItemBuilder,
              menuInitialValue: menuInitialValue,
              menuOffset: menuOffset,
              menuSelected: menuSelected,
              onSelected: onSelected
            );
          });
        return _buildButton(context, onPressed);
      }
    
      Widget _buildButton(BuildContext context, VoidCallback onPressed) {
        final _btn = IconButton(
          iconSize: iconSize ?? Theme.of(context).appBarTheme.iconTheme.size,
          icon: child,
          autofocus: autofocus ?? false,
          focusColor: focusColor,
          tooltip: tooltip,
          color: color,
          splashRadius: splashRadius,
          padding: padding ?? const EdgeInsets.all(8.0),
          onPressed: onPressed,
        );
        return minWidth == null ? _btn : ButtonTheme(
          minWidth: minWidth,
          child: _btn,
        );
      }
    
      /// 显示弹出菜单
      static void showButtonMenu<T>(BuildContext context, {
        Offset menuOffset,
        Offset local,
        List<Map<String, dynamic>> menus,
        MenusWidgetBuilder<T> menuItemBuilder,
        T menuInitialValue,
        T menuSelected,
        ValueChanged<T> onSelected,
        VoidCallback onMenuCancel,
      }) {
        final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
        final RenderBox button = context.findRenderObject() as RenderBox;
        final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
        final RelativeRect position = RelativeRect.fromRect(
          Rect.fromPoints(
            local != null ? local : button.localToGlobal(menuOffset ?? Offset(0, 40), ancestor: overlay),
            button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay),
          ),
          Offset.zero & overlay.size,
        );
        final _primaryColor = Theme.of(context).primaryColor;
        final _textStyle = Theme.of(context).popupMenuTheme?.textStyle ??
            Theme.of(context).textTheme.subtitle1;
        final List<PopupMenuEntry<T>> items = menuItemBuilder != null 
            ? menuItemBuilder(context)
            : menus.map((element) {
              final _value = element['type'];
              return PopupMenuItem<T>(
                textStyle: _textStyle,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    (menuSelected == _value)
                      ? DefaultTextStyle(
                        style: _textStyle.copyWith(color: _primaryColor),
                        child: Text(element['title']))
                      : Text(element['title']),
                    if (element['icon'] != null)
                      Icon(element['icon'], color: _primaryColor, size: 20),
                  ],
                ),
                value: _value,
              );
        }).toList();
        // Only show the menu if there is something to show
        if (items.isNotEmpty) {
          showMenu<T>(
            context: context,
            elevation: popupMenuTheme.elevation,
            items: items,
            position: position,
            shape: popupMenuTheme.shape,
            color: popupMenuTheme.color,
            initialValue: menuInitialValue,
          ).then<void>((T newValue) {
            if (newValue == null) {
              if (onMenuCancel != null) onMenuCancel();
              return null;
            }
            if (onSelected != null)
              onSelected(newValue);
            return newValue;
          });
        }
      }
    }
    
    typedef MenusWidgetBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext context);

    使用示例:

    AppBarButton(
      icon: Icon(FIcons.save),
      iconSize: 21,
      tooltip: "保存",
      onPressed: () => _saveRule(context),
    ),
    
    AppBarButton(
      icon: Icon(FIcons.share_2),
      tooltip: "分享",
      menus:  [
        {'title': '分享', 'icon': FIcons.share_2, 'type': SHARE},
        {'title': '扫码分享到远程', 'icon': FIcons.bscan, 'type': SHARE_QRSCAN},
      ],
      onSelected: (value) => onPopupMenuClick(value, provider),
    ),
    
    AppBarButton<SourceSortType>(
      icon: Icon(Icons.sort_by_alpha),
      tooltip: "排序",
      menus: [
        {'title': '按类型',
          'icon': _type == SourceSortType.type ? _axisIcon : null,
          'type': SourceSortType.type},
        {'title': '按名称',
          'icon': _type == SourceSortType.name ? _axisIcon : null,
          'type': SourceSortType.name},
        {'title': '按作者',
          'icon': _type == SourceSortType.author ? _axisIcon : null,
          'type': SourceSortType.author},
        {'title': '按分组',
          'icon': _type == SourceSortType.group ? _axisIcon : null,
          'type': SourceSortType.group},
        {'title': '按修改时间',
          'icon': _type == SourceSortType.updateTime ? _axisIcon : null,
          'type': SourceSortType.updateTime},
        {'title': '按创建时间',
          'icon': _type == SourceSortType.createTime ? _axisIcon : null,
          'type': SourceSortType.createTime},
      ],
      menuSelected: _type,
      onSelected: (value) {
        provider.sort(value, _profile)
            .whenComplete(() => refreshData(provider));
      },
    )
  • 相关阅读:
    Netty指定分隔的字符
    Netty解决TCP粘包/拆包问题
    TCP粘包/拆包问题
    Netty5-应答服务器
    线程池中的线程的排序问题
    Java NIO 概述
    在windows中,如何使用cmd命令行窗口正确显示编码为utf-8格式的文字
    OXM
    Mysql event时间触发器,实现定时修改某些符合某一条件的某一字段
    linux 环境 tomcat 莫名奇妙挂掉
  • 原文地址:https://www.cnblogs.com/yangyxd/p/14026104.html
Copyright © 2020-2023  润新知