前语

flutter 开发过程中,除了加载框之类的,dialog系列也会头疼,包括alert弹窗提示pickerdate-picker等也需求自行挑选或定制,很麻烦,且不一定能运用到自己想要的作用

这里自己更偏好ios风格,因而运用 Cupertino风格对应组件封装成了咱们直接能够用的弹窗、picker系列组件,且默许的作用也不是很好,因而自行改动了一下

本来是想运用 adaptive_dialog 弹窗,但感觉两个渠道两种风格的差点意思,个人更偏好 ios风格,所以乎才想起来独自做了 ios 风格的弹窗

然后参阅了 flutter-widget ,发现 picker 用起来也不是那么给力,就也从头定制了一下,顺道增加了比较常用风格的 pickermutli-pickerdate-picker以便于运用

事例demo(alert、action-sheet、input-alert、picker、mutli-picker、date-picker)

如果想发布到 pub.dev 能够参阅这里

本章也增加了怎么避免运用 dialog 时传递 context 的办法

ps:除此之外还增加了 ios 风格的 menu 事例,仅作为参阅,未封装成组件

ps2:如果该库和其他的姓名有抵触,能够运用模块化方法导入,liru: import 'dialogs/dialogs.dart' as dialog;

alert

ios 风格的 alert,作用如下所示

flutter-编写ios风格dialog、picker

运用如下所示,回来的成果在 Future 中,为bool类型,毕竟撤销位置也或许不是撤销而是有其他事情

showCupertinoAlert(
  // context: context,
  title: "提示",
  message: "检测到未登录,请前往登陆!",
  confirmText: "立即前往",
  cancelText: "撤销",
  // isDestructiveCancel: true,
).then((res) {
  print(res);
  setState(() {
    value = res.toString();
  });
});

总共提供了这些特点,能够根据状况传递运用

BuildContext? context, //如果没设置大局,需求传递自己的context
String title = '',
String message = '',
confirmText = '确认',
cancelText = '撤销',
isShowCancel = true,
isDestructiveConfirm = false,
isDestructiveCancel = false,

input-alert

ios 风格的 input-alert,作用如下所示

flutter-编写ios风格dialog、picker

运用如下所示,回来 Future 为输入的 text,撤销则不触发回调

showCupertinoInputAlert(
  // context: context,
  title: "标题",
  message: '内容',
  isDestructiveCancel: true,
  onChanged: (String text) {
    print(text);
    setState(() {
      value = text;
    });
  },
).then((res) {
  print(res);
  //能够干别的事情
  if (res != null) {
    setState(() {
      value = res;
    });
  }
});

详细参数如下所示,经过姓名就知道功用了,不多介绍

BuildContext? context, //如果没设置大局,需求传递自己的context
String title = '',
String message = '',
confirmText = '确认',
cancelText = '撤销',
isShowCancel = true,
isDestructiveConfirm = false,
isDestructiveCancel = false,
String? placeholder,
String? defaultText,
TextAlign? textAlign,
OverlayVisibilityMode? clearButtonMode,
TextInputType? keyboardType,
String? Function(String value)? onChanged, //内容改动后的回调,能够经过回来值来校验text输入等操作

action-sheet

ios 风格的 action-sheet,作用如下所示

flutter-编写ios风格dialog、picker

运用如下所示,回来 Future 为挑选的 index,内容能够根据 外部的actions 获取实践挑选内容,撤销不触发回调

能够经过 isDestructive、isLoading 可选参数,来为独自的按钮置红或者显现加载中,如果有未加载结束的能够运用 isLoading 参数来显现加载中的状况

final actions = <ActionSheetItem>[
  ActionSheetItem(text: "确认"),
  ActionSheetItem(text: "特殊", isDestructive: true),
  ActionSheetItem(text: "加载中...", isLoading: true),
];
showCupertinoActionSheet(
  // context: context,
  title: '演示',
  message: "请挑选一个作用",
  actions: actions, //运用了updater之后,该参数能够疏忽
  // isDestructiveCancel: true,
).then((res) {
  print(res);
  setState(() {
    value = actions[res].text!;
  });
});

如果用到了网络数,需求用到加载中,能够运用

final ActionSheetUpdater updater = ActionSheetUpdater();
getActionSheetData() {
  //有网络数据时请求主张运用该参数
  //给定默许作用
  updater.actions = <ActionSheetItem>[
    // ActionSheetItem(text: "确认"),
    // ActionSheetItem(text: "特殊", isDestructive: true),
    ActionSheetItem(text: "加载中...", isLoading: true),
  ];
  Future.delayed(const Duration(seconds: 5), () {
    setState(() {
      value = '加载完了';
    });
    //如果要用到前面的actions,能够从updater中获取
    // final actions = updater.actions;
    //也能够先赋值action后调用更新,也能够像下面似的直接传参更新
    updater.update(<ActionSheetItem>[
      ActionSheetItem(text: "确认"),
      ActionSheetItem(text: "特殊", isDestructive: true),
      ActionSheetItem(text: "加载中...", isLoading: true),
    ]);
  });
}
void showActionSheet() {
  //假设这是网络数据
  showCupertinoActionSheet(
    // context: context,
    title: '演示',
    message: "请挑选一个作用",
    // actions: actions,//运用了updater之后,该参数能够疏忽
    updater: updater,
  ).then((res) {
    print(res);
    setState(() {
      value = actions[res].text!;
    });
  });
}

参数如下所示

BuildContext? context, //如果没设置大局,需求传递自己的context
String? title,
String? message,
ActionSheetUpdater? updater, //外部声明该变量用与实时更新action
List<ActionSheetItem>? actions,
isShowCancel = true,
cancelText = '撤销',
isDefaultActionCancel = false, //粗体
isDestructiveCancel = false, //红色

picker

单列picker,作用如下所示

flutter-编写ios风格dialog、picker

运用如下所示,需求传入一维数组,默许索引不传递则为0,成果回来索引index挑选的item

showCupertinoPicker(
  // context: context,
  pickerList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
  defaultIndex: 2,
  onValueChanged: (index, item) {
    //picker滑动的回调
    print(index);
    print(item);
  },
).then((res) {
  setState(() {
    value = res.item.toString();
  });
  print(res.index);
  print(res.item);
});

参数如下所示

BuildContext? context, //如果没设置大局,需求传递自己的context
required List pickerList,
defaultIndex = 0,
String? title = '',
String? confirmText = '确认',
String? cancelText = '撤销',
Color? confirmColor,
Color? cancelColor,
void Function(int index, dynamic item)? onValueChanged,

mutli-picker

多列picker,根据需求运用,作用如下所示

flutter-编写ios风格dialog、picker

picker 相似,只不过传递的数据源为多维数组(主张不超过6组),成果回来:数据源(pickerList)、索引(indexs)、挑选的列表(selectList)

showCupertinoMutliPicker(
  // context: context,
  pickerList: [
    [100, 200, 300, 400, 500],
    [10, 20, 30, 40, 50],
    [1, 2, 3, 4, 5],
  ],
  onValueChanged: (indexs) {
    print(indexs);
  },
).then((res) {
  setState(() {
    value = res.selectList.join(',');
  });
  print(res);
});

参数如下所示,跟 picker 相似

BuildContext? context, //如果没设置大局,需求传递自己的context
required List<List> pickerList,
List<int>? defaultIndex,
List<String>? units, //单位,结尾是否运用单位,传入就显现
String? title = '',
String? confirmText = '确认',
String? cancelText = '撤销',
Color? confirmColor,
Color? cancelColor,
double? itemFontSize,
void Function(List<int> index)? onValueChanged, //因为pickerList外面传递进来的,因而里面不在回来其他

date-picker

时刻挑选器 date-picker,一个挑选日期时刻的挑选器,能够设置日期区间,因为是直接展示弹窗的方法,不便于修正,直接运用新的 mutli-picker 编写的,而不是根据前面的 mutli-picker,也间接提高了功率

flutter-编写ios风格dialog、picker

运用作用如下所示,成果回来:数据源(pickerList)、索引(indexs)、挑选的列表(selectList),拼接的日期字符串(dateString)

void showIosStyleDatePicker(DatePickerType pickerMode) {
  showCupertinoDatePicker(
    // context: context,
    pickerMode: pickerMode,
    beforeYearsInterval: 10,
    afterYearsInterval: 10,
    // minDate: DateTime.now().subtract(const Duration(days: 1000)),
    // maxDate: DateTime.now().add(const Duration(days: 1000)),
  ).then((res) {
    print(res);
    setState(() {
      value = res.dateString;
    });
  });
}

运用如下所示,其间 units 为单位,时刻距离看下面参数即可,都是成双成对出现,要不都不传递运用默许

BuildContext? context, //如果没设置大局,需求传递自己的context
DatePickerType pickerMode = DatePickerType.dateTimeMinute,
List<int>? defaultIndexs,
List<String>? units = const [], //如果想运用自己的单位正常传递,如果不显现默许单位传递null即可
DateTime? dateTime, //传入时刻,能够操控默许挑选位置,默许当时时刻
DateTime? minDate, //能够设置前后截止时刻,优先该特点
DateTime? maxDate,
int? beforeYearsInterval, //根据当时时刻或者传入时刻向前向后多少年,默许前后20年,以一年365天为基准
int? afterYearsInterval,
String title = '',
confirmText = '确认',
cancelText = '撤销',
double? itemFontSize,
Color? confirmColor,
Color? cancelColor,

menu(用的不多也介绍一下)

看一下作用图

flutter-编写ios风格dialog、picker

运用如下所示,外面需求放一个 ContainerSizeBox 操控外部点击的按钮大小,显现作用就如上所示,因为运用比较少,且功用比较简略,定制多余,就不封装了

CupertinoContextMenu(
  actions: <Widget>[
    CupertinoContextMenuAction(
      onPressed: () {
        Navigator.pop(context);
      },
      isDefaultAction: true,
      trailingIcon: CupertinoIcons.doc_on_clipboard_fill,
      child: const Text('Copy'),
    ),
    CupertinoContextMenuAction(
      onPressed: () {
        Navigator.pop(context);
      },
      trailingIcon: CupertinoIcons.share,
      child: const Text('Share'),
    ),
    CupertinoContextMenuAction(
      onPressed: () {
        Navigator.pop(context);
      },
      trailingIcon: CupertinoIcons.heart,
      child: const Text('Favorite'),
    ),
    CupertinoContextMenuAction(
      onPressed: () {
        Navigator.pop(context);
      },
      isDestructiveAction: true,
      trailingIcon: CupertinoIcons.delete,
      child: const Text('Delete'),
    ),
  ],
  child: Container(
    decoration: BoxDecoration(
      color: CupertinoColors.systemYellow,
      borderRadius: BorderRadius.circular(20.0),
    ),
    child: const FlutterLogo(size: 500.0),
  ),

不传递context方法弹出dialog

事例运用的是计划一,预留该类,方便后续添加设置特点(事例中并没有扩展其他特点,仅仅预留运用)

计划一 InheritedWidget + navigatorKey

咱们运用 InheritedWidget + navigatorKey来解决即可

InheritedWidget 设置,为了方便运用,运用了静态变量

class DialogConfig extends InheritedWidget {
  static GlobalKey<NavigatorState>? globalNavigatorKey;
  const DialogConfig.internal({
    Key? key,
    required Widget child,
  }) : super(key: key, child: child);
  factory DialogConfig({
    Key? key,
    required GlobalKey<NavigatorState> globalNavigatorKey,
    required Widget child,
  }) {
    DialogConfig.globalNavigatorKey = globalNavigatorKey;
    return DialogConfig.internal(
      key: key,
      child: child,
    );
  }
  /*
    GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
    DialogConfig(
      globalNavigatorKey: navigatorKey,
      child: MaterialApp(
        navigatorKey: navigatorKey,
        ...,
      ),
    );
   */
  static BuildContext get context {
    assert(globalNavigatorKey?.currentState?.context != null, '获取globalcontext失败,请在main函数中的 MaterialApp 外层,设置 DialogConfig,且传入MaterialApp的navigatorKey');
    return globalNavigatorKey!.currentState!.context;
  }
  //就像 Navigator.of(context)一样获取,用于调用内部参数
  static DialogConfig of(BuildContext context) {
    final DialogConfig? result = context.dependOnInheritedWidgetOfExactType<DialogConfig>();
    assert(result != null, 'No DialogConfig found in context');
    return result!;
  }
  @override
  bool updateShouldNotify(DialogConfig oldWidget) {
    return false;
  }
}

MaterialApp 所在类

GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
DialogConfig(
  globalNavigatorKey: navigatorKey,
  child: MaterialApp(
    navigatorKey: navigatorKey,
    debugShowCheckedModeBanner: false,
    title: 'Flutter Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: const MyHomePage(),
  ),
);

计划二 单例类 + navigatorKey(简略粗犷)

创立一个保存单例类, 然后 MaterialApp 所在出往单例类传入navigatorKey参数即可

class DialigConfig {
  static GlobalKey<NavigatorState>? globalNavigatorKey;
  static BuildContext get context {
    assert(globalNavigatorKey?.currentState?.context != null, '获取globalcontext失败,请在 MaterialApp 所在处,设置 DialogConfig,且传入MaterialApp的navigatorKey');
    return globalNavigatorKey!.currentState!.context;
  }
}

然后直接传入即可

DialigConfig1.globalNavigatorKey = navigatorKey;

最后

能够直接拉进自己的代码仓库运用,快来试试吧