在评委项目开发中我使用到的弹窗dialog与modal sheet底部弹出组件,我对其进行了基础的封装,以适用于本项目,代码如下:
class JudgeDialog {
// 内容边距
static EdgeInsetsGeometry _contentPadding(String? content) {
return content != null
? EdgeInsets.only(
left: 30.rpx,
right: 30.rpx,
top: 32.rpx,
bottom: 26.rpx,
)
: EdgeInsets.all(0.rpx);
}
// 内容样式
static TextStyle _contentStyle() {
return TextStyle(
fontSize: 31.rpx,
height: 1.5,
fontFamily: AppTextFamily.appleMiddle,
color: AppColors.t3,
);
}
// X关闭按钮
static Widget closeBtn(
BuildContext context, {
Function? closeCall,
}) {
return Positioned(
right: 36.rpx,
top: 30.rpx,
child: SizedBox(
height: 36.rpx,
width: 36.rpx,
child: IconButton(
onPressed: () {
if (closeCall != null) {
closeCall();
} else {
Navigator.pop(context);
}
},
padding: EdgeInsets.all(0.rpx),
icon: Transform.rotate(
angle: pi / 4,
child: Icon(
AppIcon.plusAdd,
size: 36.rpx,
color: AppColors.t9,
),
),
),
),
);
}
static Future<bool?> tipDialog(
BuildContext context, {
bool? dismissible = false, // 背景与滑动是否可关
bool? close = false, // 是否有X的关闭
Function? closeCall, // 关闭X时调用
Color? bgColor, // 背景色
double? opacity,
String? title,
String? content,
Widget? contentChild,
EdgeInsetsGeometry? contentPadding,
TextAlign? textAlign,
double? btnWidth,
String? confirmText = '我知道了',
Function? confirm,
String? cancelText = '取消',
Function? cancel,
}) async {
return showDialog(
context: context,
barrierDismissible: dismissible!,
barrierColor: bgColor ?? Colors.black.withOpacity(opacity ?? 0.55),
builder: (dialogContext) {
return WillPopScope(
onWillPop: () async {
return dismissible;
},
child: Container(
width: HYSizeFit.screenWidth,
padding: EdgeInsets.only(
left: HYSizeFit.screenWidth * 0.1,
right: HYSizeFit.screenWidth * 0.1,
bottom: HYSizeFit.screenHeight * 0.1,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AlertDialog(
insetPadding: EdgeInsets.all(0.rpx),
backgroundColor: Colors.white,
titlePadding: EdgeInsets.all(0.rpx),
buttonPadding: EdgeInsets.all(0.rpx),
alignment: Alignment.center,
elevation: 0.rpx,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.rpx), // 设置圆角大小
),
title: Stack(
children: [
Container(
alignment: Alignment.center,
padding: EdgeInsets.only(top: 52.rpx),
child: AppTextTitle.titleH34(
title ?? '提示',
fontFamily: AppTextFamily.appleBold,
),
),
if (close!)
closeBtn(
dialogContext,
closeCall: closeCall,
),
],
),
contentPadding: contentPadding ?? _contentPadding(content),
contentTextStyle: _contentStyle(),
content: content != null
? Text(content, textAlign: textAlign ?? TextAlign.center)
: contentChild ?? const SizedBox.shrink(),
actionsAlignment: MainAxisAlignment.spaceAround,
actionsPadding: EdgeInsets.only(
top: content != null ? 26.rpx : 0.rpx,
left: 30.rpx,
right: 30.rpx,
bottom: 52.rpx,
),
actions: [
if (cancel != null)
GlobalButton.customButton(
'$cancelText',
bgColor1: AppColors.page,
bgColor2: AppColors.page,
color: AppColors.t3,
width: btnWidth ?? 210.rpx,
height: 70.rpx,
radius: 35.rpx,
size: 30.rpx,
ripple: false,
onPressed: () => cancel(),
),
GlobalButton.customButton(
'$confirmText',
bgColor1: AppColors.primary,
bgColor2: AppColors.primary,
width: btnWidth ?? 210.rpx,
height: 70.rpx,
radius: 35.rpx,
size: 30.rpx,
ripple: false,
onPressed: () {
if (confirm == null) {
Navigator.pop(context, true);
} else {
confirm();
}
},
),
],
),
],
),
),
);
},
);
}
// 自定义弹窗
static Future<bool?> markDialog(
BuildContext context, {
bool? dismissible = false,
double? opacity,
Color? bgColor, // 背景色
required Widget child,
}) async {
return showDialog(
context: context,
barrierDismissible: dismissible!,
barrierColor: bgColor ?? Colors.black.withOpacity(opacity ?? 0.55),
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
return dismissible;
},
child: SizedBox(
width: HYSizeFit.screenWidth,
height: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [child],
),
),
);
},
);
}
/// 底部弹窗
static Future<bool?> modalSheet(
BuildContext context, {
bool? dismissible = true,
double? opacity,
Color? barrierColor, // 弹窗背景色
Color? bgColor, // 弹出背景
double? elevation,
BoxConstraints? constraints,
required Widget child,
}) async {
return showModalBottomSheet(
context: context,
elevation: elevation,
constraints: constraints,
backgroundColor: bgColor ?? Colors.transparent,
barrierColor: barrierColor ?? Colors.black.withOpacity(opacity ?? 0.6),
builder: (BuildContext context) {
return child;
},
);
}
}
在此基础上还增加了底部支付选择的弹窗:
// 支付方式
enum PayMethodType {
/// 支付宝
alipay,
/// 评贝
pingBei
}
/// 全局支付方式选择
class AppPayModalSheet extends StatefulWidget {
const AppPayModalSheet({
super.key,
required this.title,
this.amount,
this.beiAmount,
this.balance,
this.enough,
this.unit,
required this.methods,
this.onPressed,
});
final String title; // 弹窗标题
final String? amount; // 金额
final String? beiAmount; // 评贝金额
final String? balance; // 本人评贝余额
final bool? enough; // 评贝余额是否不足
final String? unit; // 单位
final Set<PayMethodType> methods; // 支付方式
final ValueChanged? onPressed;
@override
State<AppPayModalSheet> createState() => _AppPayModalSheetState();
}
class _AppPayModalSheetState extends State<AppPayModalSheet> {
late Set<PayMethodType> methods;
late PayMethodType group;
@override
void initState() {
super.initState();
methods = widget.methods;
group = methods.first;
setState(() {});
}
@override
Widget build(BuildContext context) {
return IntrinsicHeight(
child: Container(
width: HYSizeFit.screenWidth,
padding: EdgeInsets.only(bottom: HYSizeFit.bottomHeight + 10.rpx),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.rpx),
topRight: Radius.circular(30.rpx),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
PayModalTitle(title: widget.title),
Column(
mainAxisSize: MainAxisSize.min,
children: methods.map((e) {
return PayByModalMethod(
value: e,
group: group,
balance: widget.balance,
enough: widget.enough,
changed: (val) {
group = val;
if (mounted) setState(() {});
},
);
}).toList(),
),
PayBottomBtn(
leftChild: group == PayMethodType.pingBei ? beiBy() : priceBy(),
onPressed: () {
widget.onPressed!(group);
},
),
],
),
),
);
}
// 金额
Widget priceBy() {
return RichText(
text: TextSpan(
text: '¥',
style: AppTextTitle.customStyle(height: 1.2, size: 32.rpx),
children: [
TextSpan(
text: '${widget.amount}',
style: AppTextTitle.customStyle(
height: 1.2,
size: 40.rpx,
fontFamily: AppTextFamily.pingFangHeavy,
),
),
],
),
);
}
// 评贝
Widget beiBy() {
return RichText(
text: TextSpan(
text: '${widget.beiAmount}',
style: AppTextTitle.customStyle(
height: 1.2,
size: 40.rpx,
fontFamily: AppTextFamily.pingFangHeavy,
),
children: [
TextSpan(
text: widget.unit ?? '评贝',
style: AppTextTitle.customStyle(
height: 1.2, size: 32.rpx, letterSpacing: -2.rpx),
),
],
),
);
}
}
/// 支付弹窗标题
class PayModalTitle extends StatelessWidget {
const PayModalTitle({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Container(
width: double.maxFinite,
padding: EdgeInsets.only(top: 42.rpx, bottom: 50.rpx),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.rpx),
topRight: Radius.circular(30.rpx),
),
gradient: AppColors.multicolor(['#FFEFCD', '#FFEFCD', '#FFFFFF']),
),
child: Text(
title,
style: AppTextTitle.customStyle(
height: 1.2,
size: 38.rpx,
color: ColorUtil.stringColor('#A07313'),
fontFamily: AppTextFamily.appleBold,
),
),
);
}
}
/// 支付底部按钮区
class PayBottomBtn extends StatelessWidget {
const PayBottomBtn({
super.key,
this.leftChild,
this.mainAxisAlignment,
this.btnText,
this.onPressed,
});
final Widget? leftChild;
final MainAxisAlignment? mainAxisAlignment;
final String? btnText;
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return Container(
height: 112.rpx,
width: double.maxFinite,
padding: EdgeInsets.only(left: 56.rpx, right: 30.rpx),
margin: EdgeInsets.only(top: 30.rpx),
decoration: BoxDecoration(
border: Border(
top: BorderSide(width: 1.rpx, color: AppColors.line),
),
),
child: Row(
mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
leftChild ?? const SizedBox.shrink(),
GlobalButton.elevatedButton(
btnText ?? '支付',
width: 216.rpx,
height: 80.rpx,
size: 32.rpx,
ripple: false,
radius: 6.rpx,
bgColor: ColorUtil.stringColor('#EDA830'),
onPressed: onPressed,
),
],
),
);
}
}
/// 支付方法选项
class PayByModalMethod extends StatelessWidget {
const PayByModalMethod({
super.key,
this.balance,
this.enough,
this.value,
this.group,
this.changed,
});
final PayMethodType? value;
final PayMethodType? group;
final String? balance; // 本人评贝余额
final bool? enough; // 评贝余额是否不足
final ValueChanged? changed;
@override
Widget build(BuildContext context) {
bool isEnough = value == PayMethodType.pingBei && enough != null && enough!;
String payText() {
switch (value) {
case PayMethodType.alipay:
return AdaptStr.strHansen();
case PayMethodType.pingBei:
return '评贝${AdaptStr.sef()}';
default:
return '';
}
}
return Container(
width: HYSizeFit.screenWidth,
height: 100.rpx,
padding: EdgeInsets.only(left: 46.rpx, right: 40.rpx),
child: InkWell(
onTap: () {
if (!isEnough) {
changed!(value);
}
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
payIcon(),
AppTextTitle.titleH2(payText(), letterSpacing: -2.rpx),
if (value == PayMethodType.pingBei && balance != null)
AppTextTitle.titleH2(
'(余额:$balance评贝)',
letterSpacing: -1.5.rpx,
),
],
),
isEnough
? AppTextTitle.titleH3('评贝余额不足', color: AppColors.primary)
: SizedBox(
width: 100.rpx,
child: value == group
? Icon(
JudgesIcon.radioYes,
size: 34.rpx,
color: ColorUtil.stringColor('#4FCD3E'),
)
: Icon(
JudgesIcon.radioNot,
size: 34.rpx,
color: ColorUtil.stringColor('#CCCCCC'),
),
),
],
),
),
);
}
// 支付方式icon
Widget payIcon() {
if (value == PayMethodType.alipay) {
return Padding(
padding: EdgeInsets.only(right: 20.rpx),
child: Icon(
JudgesIcon.payAli,
size: 48.rpx,
color: ColorUtil.stringColor('00a0ea'),
),
);
} else {
return Padding(
padding: EdgeInsets.only(right: 20.rpx),
child: Image.asset(
'${AppGlobal.imgUrl}beiby.png',
height: 48.rpx,
fit: BoxFit.fitHeight,
),
);
}
}
}