记录一下flutter项目自己封窗的弹窗

发布于:2025-05-21 ⋅ 阅读:(16) ⋅ 点赞:(0)

在评委项目开发中我使用到的弹窗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,
        ),
      );
    }
  }
}