1. 程式人生 > 實用技巧 >[Flutter] 擴充套件一個支援彈出選單的IconButton

[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));
  },
)