zoukankan      html  css  js  c++  java
  • [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));
      },
    )
  • 相关阅读:
    Begin Example with Override Encoded SOAP XML Serialization
    State Machine Terminology
    How to: Specify an Alternate Element Name for an XML Stream
    How to: Publish Metadata for a WCF Service.(What is the Metadata Exchange Endpoint purpose.)
    Beginning Guide With Controlling XML Serialization Using Attributes(XmlSerializaiton of Array)
    Workflow 4.0 Hosting Extensions
    What can we do in the CacheMetaData Method of Activity
    How and Why to use the System.servicemodel.MessageParameterAttribute in WCF
    How to: Begin Sample with Serialization and Deserialization an Object
    A Test WCF Service without anything of config.
  • 原文地址:https://www.cnblogs.com/yangyxd/p/14026104.html
Copyright © 2011-2022 走看看