Flutter 仿iOS Sheet Action 弹窗封装

前因

项目开发中总有一些常用的界面,需要能够重复利用,于是,有了这篇文章,期望效果如下:

需求分析

  1. 底部取消按钮,字体颜色单独显示
  2. 选项按钮并列显示,可能存在多个
  3. 选择后需要有对应的结果返回
  4. 整个弹窗背景色透明

根据以上需求,设计组件需要开放的属性如下:

arduino 复制代码
final String cancelTitle;//取消按钮标题
final String? title;//提示标题,此版本暂未实现显示标题
final List<String> actions;//选项按钮标题
final Color? cancelColor;//取消按钮颜色
final Color? titleColor;//标题颜色
final Color? actionColor;//选项标题颜色
final Color bgColor;//选项卡背景色
final String? fontFamily;//字体
final double fontSize;//字号
final double  selectionHeight;//每个选项的高度
final double borderRadius;//圆角大小
final FontWeight fontWeight;//字重
final ValueChanged<int>? selectAction;//选项结果回调

效果实现

拆分模块:

  1. 整体使用一个SizeBox包装,控制组件大小
  2. 整体展现形式为列表,先用一个Column包装
  3. 考虑到上层选项卡共用一个圆角和下层选项卡单独一个圆角,将使用两个Column将上下层拆分
  4. 圆角效果则用ClipRRect包裹
  5. 每个选项卡选中事件则直接用GestureDetector包装,返回点击事件
  6. 考虑到每个选项卡有背景色,所有用Container包裹一下,展示背景色
  7. 文字显示部分则直接使用Text这里用的SVText是对Text组件的封装,是为了便于开发和后期的修改,其本质就是一个Text,开放了一些常用属性。

上代码:

less 复制代码
Widget build(BuildContext context) {
  // TODO: implement build
  double width =  MediaQuery.of(context).size.width*0.9;//计算需要展示的宽度
  double safeBottomHeight = MediaQuery.of(context).padding.bottom;//底部需要留出安全区的位置
  double space = 10;//取消选项和其他选项的间距
  return SizedBox(//使用SizeBox进行包装
      width: width,//宽
      height: (selectionHeight + 1)*(actions.length + 1) + safeBottomHeight + space,//高
      child: Column(//分层
        children: [
          ClipRRect(//上层圆角
            borderRadius: BorderRadius.all(Radius.circular(borderRadius)),//圆角
            child: Column(//上层显示按钮
            //这里为了减少代码量,则直接将生成选项卡的代码逻辑封装了一下
              children: getActionSheet(context),
            ),
          ),
          Padding(//上层和取消选项间距
              padding: EdgeInsets.only(top: space),
          ),
          ClipRRect(//下层圆角
            borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
            //封装选项卡生成函数
            child: getSelectWidget(cancelTitle, true, -1,context),
          ),
          Padding(
              padding: EdgeInsets.only(top: safeBottomHeight),
          ),
        ],
      )
  );
}
//返回上层选项卡所有的视图
List<Widget> getActionSheet(BuildContext context){
  List<Widget>  actionsWidget = [];
  for (int i = 0;i < actions.length; i ++){
    String title = actions[i];
    //生成上层每个选项卡
    GestureDetector action = getSelectWidget(title, false, i, context);
    actionsWidget.add(action);
    if (i < actions.length - 1){
      Padding padding = const Padding(
          padding: EdgeInsets.only(top: 1)
      );
      actionsWidget.add(padding);
    }
  }
  return actionsWidget;
}
//选项卡生成函数
GestureDetector getSelectWidget(String title, bool isCancel, int tag, BuildContext context){
  return GestureDetector(//点击事件包裹
    child: Container(
      height: selectionHeight,
      width: double.infinity,
      color: bgColor,
      alignment: Alignment.center,
      child: SSLText(
        text: title,
        textColor: isCancel?cancelColor:actionColor,
        fontSize: fontSize,
        fontWeight: fontWeight,
        textDecoration: TextDecoration.none,
      ),
    ),
    onTap: (){
    //点击回调事件
      if (selectAction != null){
        selectAction!(tag);
      }
      //由于采用的是弹窗形式,则可使用pop获取返回值
      Navigator.of(context).pop(tag);
    },
  );
}

至此弹窗基本封装完毕。由于目前未涉及到使用标题的弹窗,标题功能暂未开发。

弹窗使用

弹窗封装完成后还需要有函数去展示这个弹窗,这里参考Flutter 对话框Flutter 弹窗样式。 最后选择使用showCupertinoModalPopup,这个弹窗的效果和iOS类似,只需将封装好的弹窗传递过去就好,剩下的动画效果和返回值系统已经处理好,直接用就行了。

上代码:

php 复制代码
static Future<int> showBottomSheet(BuildContext context,
    { required List<String> actions,
      required String cancelTitle,
      Color? titColor,
      Color? cancelColor,
      Color? actionColor,
      double? fontSize,
      String? fontFamily,
      double? selectionHeight,
      double? borderRadius,
      Color? bgColor,
      String? title,
      ValueChanged? selection,
    })async {
  //通过系统获取返回值,具体的可以研究下弹窗的原理,这个就是获取上面pop的返回值
  var result = await showCupertinoModalPopup(context: context, builder: (context){
    return SSLSheetAlert(//封装好的弹窗
      actions: actions,
      cancelTitle: cancelTitle,
      selectAction: selection,
      titleColor: titColor,
      cancelColor: cancelColor,
      actionColor: actionColor,
      fontSize: fontSize ?? 16,
      fontFamily: fontFamily,
      selectionHeight: selectionHeight??50,
      borderRadius: borderRadius??8,
      bgColor: bgColor??Colors.white,
      title: title,
    );
  });
  //需要额外处理的是,如果直接点击背景罩,此时返回的是null,所以这里需要特殊判断一下
  if (result == null){
    return -1;
  }
  return result;
}

最终用法:

php 复制代码
//获取系统返回结果
int result = await UIAlertTool.svShowBottomSheet(
    context,
    actions:["男", "女"],
    cancelTitle: "取消",
    actionColor: Colors.green,
    cancelColor: Colors.red,
    selection: (value){
    获取回调结果
      debugPrint("ssl get result 11 $value");
    }
);
debugPrint("ssl get result $result");

最终效果:

iOS

Android

注意:

在没有使用Material的时候,Text文本显示会和使用Material有区别,说下两个点:

  1. 没有使用的时候文本下面会有黄色的下划线,此时需要使用TextStyle中的decoration属性,设置为TextDecoration.none
  2. 字重在没有使用Material的时候会加粗,这个时候需要设置一下字重,通常设置为FontWeight.w500
相关推荐
美狐美颜sdk4 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭9 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
泓博10 小时前
Objective-c把字符解析成字典
开发语言·ios·objective-c
Daniel_Coder10 小时前
Xcode 中常用图片格式详解
ios·xcode·swift
瓜子三百克11 小时前
Objective-C 路由表原理详解
开发语言·ios·objective-c
帅次11 小时前
Objective-C面向对象编程:类、对象、方法详解(保姆级教程)
flutter·macos·ios·objective-c·iphone·swift·safari
小蜜蜂嗡嗡11 小时前
flutter flutter_vlc_player播放视频设置循环播放失效、初始化后获取不到视频宽高
flutter
RyanGo13 小时前
iOS断点下载
ios·swift
孤鸿玉13 小时前
[Flutter小技巧] Row中widget高度自适应的几种方法
flutter
bawomingtian12313 小时前
FlutterView 源码解析
flutter