前因
项目开发中总有一些常用的界面,需要能够重复利用,于是,有了这篇文章,期望效果如下:
需求分析
- 底部取消按钮,字体颜色单独显示
- 选项按钮并列显示,可能存在多个
- 选择后需要有对应的结果返回
- 整个弹窗背景色透明
根据以上需求,设计组件需要开放的属性如下:
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;//选项结果回调
效果实现
拆分模块:
- 整体使用一个SizeBox包装,控制组件大小
- 整体展现形式为列表,先用一个Column包装
- 考虑到上层选项卡共用一个圆角和下层选项卡单独一个圆角,将使用两个Column将上下层拆分
- 圆角效果则用ClipRRect包裹
- 每个选项卡选中事件则直接用
GestureDetector
包装,返回点击事件 - 考虑到每个选项卡有背景色,所有用
Container
包裹一下,展示背景色 - 文字显示部分则直接使用
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有区别,说下两个点:
- 没有使用的时候文本下面会有黄色的下划线,此时需要使用TextStyle中的
decoration
属性,设置为TextDecoration.none
。 - 字重在没有使用Material的时候会加粗,这个时候需要设置一下字重,通常设置为
FontWeight.w500
。