在这里插入图片描述

前言

弹窗对话框是商城应用中重要的交互组件,用于确认操作、展示提示信息、收集用户输入等场景。一个设计良好的弹窗组件需要在吸引用户注意力的同时不造成过度打扰,并提供清晰的操作指引。本文将详细介绍如何在Flutter和OpenHarmony平台上开发各类弹窗对话框组件,包括确认弹窗、提示弹窗、输入弹窗等常见类型。

弹窗的设计需要遵循最小干扰原则,只在必要时才弹出,并且提供明确的关闭方式。弹窗内容应该简洁明了,让用户能够快速理解并做出决策。同时,弹窗的视觉设计需要与应用整体风格保持一致,营造统一的用户体验。

Flutter确认弹窗组件

首先实现最常用的确认弹窗:

class ConfirmDialog extends StatelessWidget {
  final String title;
  final String content;
  final String? cancelText;
  final String? confirmText;
  final VoidCallback? onCancel;
  final VoidCallback? onConfirm;
  final bool isDanger;

  const ConfirmDialog({
    Key? key,
    required this.title,
    required this.content,
    this.cancelText,
    this.confirmText,
    this.onCancel,
    this.onConfirm,
    this.isDanger = false,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      child: Container(
        width: 280,
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildTitle(),
            const SizedBox(height: 12),
            _buildContent(),
            const SizedBox(height: 20),
            _buildButtons(context),
          ],
        ),
      ),
    );
  }
}

ConfirmDialog组件用于需要用户确认的操作场景,如删除商品、取消订单等。title是弹窗标题,content是提示内容,cancelText和confirmText分别是取消和确认按钮的文字。isDanger标记是否为危险操作,危险操作的确认按钮会使用红色。Dialog使用圆角矩形外观,Container设置固定宽度和内边距。Column使用mainAxisSize.min使高度自适应内容。

标题组件:

Widget _buildTitle() {
  return Text(
    title,
    style: const TextStyle(
      fontSize: 17,
      fontWeight: FontWeight.w600,
      color: Color(0xFF333333),
    ),
    textAlign: TextAlign.center,
  );
}

Widget _buildContent() {
  return Text(
    content,
    style: const TextStyle(
      fontSize: 14,
      color: Color(0xFF666666),
      height: 1.5,
    ),
    textAlign: TextAlign.center,
  );
}

标题使用17像素粗体居中显示,在视觉上突出弹窗的主题。内容使用14像素灰色文字,1.5倍行高确保多行文字清晰易读。两者都使用居中对齐,与弹窗的整体布局保持一致。这种设计让用户能够快速理解弹窗的目的和内容。

按钮组件:

Widget _buildButtons(BuildContext context) {
  return Row(
    children: [
      Expanded(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pop();
            onCancel?.call();
          },
          child: Container(
            height: 44,
            decoration: BoxDecoration(
              color: const Color(0xFFF5F5F5),
              borderRadius: BorderRadius.circular(22),
            ),
            alignment: Alignment.center,
            child: Text(
              cancelText ?? '取消',
              style: const TextStyle(
                fontSize: 15,
                color: Color(0xFF666666),
              ),
            ),
          ),
        ),
      ),
      const SizedBox(width: 12),
      Expanded(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pop();
            onConfirm?.call();
          },
          child: Container(
            height: 44,
            decoration: BoxDecoration(
              color: isDanger 
                ? const Color(0xFFE53935) 
                : const Color(0xFFE53935),
              borderRadius: BorderRadius.circular(22),
            ),
            alignment: Alignment.center,
            child: Text(
              confirmText ?? '确定',
              style: const TextStyle(
                fontSize: 15,
                color: Colors.white,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
        ),
      ),
    ],
  );
}

按钮区域包含取消和确认两个按钮,使用Expanded平分宽度。取消按钮使用灰色背景,确认按钮使用红色背景。两个按钮都使用圆角胶囊形状,44像素高度确保足够的点击区域。点击按钮时先关闭弹窗再执行回调,避免回调中的操作影响弹窗关闭。这种设计让用户能够清晰地区分主要和次要操作。

弹窗显示方法

class DialogHelper {
  static Future<bool?> showConfirm(
    BuildContext context, {
    required String title,
    required String content,
    String? cancelText,
    String? confirmText,
    bool isDanger = false,
  }) {
    return showDialog<bool>(
      context: context,
      barrierDismissible: false,
      builder: (context) => ConfirmDialog(
        title: title,
        content: content,
        cancelText: cancelText,
        confirmText: confirmText,
        isDanger: isDanger,
        onCancel: () => Navigator.of(context).pop(false),
        onConfirm: () => Navigator.of(context).pop(true),
      ),
    );
  }
}

DialogHelper工具类封装了弹窗的显示方法,简化调用代码。showConfirm方法返回Future<bool?>,调用者可以通过await获取用户的选择结果。barrierDismissible设为false禁止点击遮罩关闭弹窗,强制用户做出选择。这种封装方式使弹窗的使用更加便捷,代码更加简洁。

提示弹窗组件

class ToastOverlay extends StatelessWidget {
  final String message;
  final IconData? icon;
  final Color? iconColor;

  const ToastOverlay({
    Key? key,
    required this.message,
    this.icon,
    this.iconColor,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: const EdgeInsets.symmetric(
          horizontal: 24,
          vertical: 16,
        ),
        decoration: BoxDecoration(
          color: Colors.black.withOpacity(0.8),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (icon != null) ...[
              Icon(icon, size: 36, color: iconColor ?? Colors.white),
              const SizedBox(height: 12),
            ],
            Text(
              message,
              style: const TextStyle(
                fontSize: 14,
                color: Colors.white,
              ),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}

ToastOverlay组件用于显示轻量级的提示信息,如操作成功、加载中等。message是提示文字,icon是可选的图标,iconColor是图标颜色。Container使用半透明黑色背景和圆角,在各种页面背景上都清晰可见。Column垂直排列图标和文字,条件渲染确保只有当icon存在时才显示图标区域。这种设计简洁明了,不会过度打扰用户。

Toast显示方法:

class ToastHelper {
  static OverlayEntry? _overlayEntry;
  
  static void show(
    BuildContext context,
    String message, {
    IconData? icon,
    Color? iconColor,
    Duration duration = const Duration(seconds: 2),
  }) {
    _overlayEntry?.remove();
    
    _overlayEntry = OverlayEntry(
      builder: (context) => ToastOverlay(
        message: message,
        icon: icon,
        iconColor: iconColor,
      ),
    );
    
    Overlay.of(context).insert(_overlayEntry!);
    
    Future.delayed(duration, () {
      _overlayEntry?.remove();
      _overlayEntry = null;
    });
  }
  
  static void showSuccess(BuildContext context, String message) {
    show(context, message, 
      icon: Icons.check_circle, 
      iconColor: const Color(0xFF4CAF50),
    );
  }
  
  static void showError(BuildContext context, String message) {
    show(context, message, 
      icon: Icons.error, 
      iconColor: const Color(0xFFE53935),
    );
  }
}

ToastHelper工具类管理Toast的显示和隐藏。使用OverlayEntry将Toast插入到页面最上层,不影响页面的正常交互。_overlayEntry静态变量确保同时只显示一个Toast,新Toast会替换旧的。Future.delayed在指定时间后自动移除Toast。showSuccess和showError是便捷方法,预设了成功和错误的图标和颜色。

输入弹窗组件

class InputDialog extends StatefulWidget {
  final String title;
  final String? hintText;
  final String? initialValue;
  final int maxLength;
  final ValueChanged<String>? onConfirm;

  const InputDialog({
    Key? key,
    required this.title,
    this.hintText,
    this.initialValue,
    this.maxLength = 100,
    this.onConfirm,
  }) : super(key: key);

  
  State<InputDialog> createState() => _InputDialogState();
}

class _InputDialogState extends State<InputDialog> {
  late TextEditingController _controller;

  
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.initialValue);
  }

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      child: Container(
        width: 300,
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildTitle(),
            const SizedBox(height: 16),
            _buildInput(),
            const SizedBox(height: 20),
            _buildButtons(),
          ],
        ),
      ),
    );
  }
}

InputDialog组件用于收集用户输入,如修改昵称、添加备注等场景。title是弹窗标题,hintText是输入框占位提示,initialValue是初始值,maxLength是最大输入长度。组件使用StatefulWidget管理输入框的文本控制器。initState中初始化控制器,dispose中释放资源。这种设计让用户可以方便地输入和编辑文本。

输入框组件:

Widget _buildInput() {
  return TextField(
    controller: _controller,
    maxLength: widget.maxLength,
    decoration: InputDecoration(
      hintText: widget.hintText,
      hintStyle: const TextStyle(
        fontSize: 14,
        color: Color(0xFFCCCCCC),
      ),
      contentPadding: const EdgeInsets.all(12),
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8),
        borderSide: const BorderSide(color: Color(0xFFEEEEEE)),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(8),
        borderSide: const BorderSide(color: Color(0xFFE53935)),
      ),
      counterStyle: const TextStyle(
        fontSize: 12,
        color: Color(0xFF999999),
      ),
    ),
  );
}

输入框使用TextField组件,maxLength限制最大输入长度并显示字数统计。InputDecoration配置输入框的外观样式,包括占位提示、内边距、边框等。focusedBorder设置获得焦点时的边框颜色为红色,提供清晰的焦点反馈。counterStyle设置字数统计的样式。这种设计让用户能够清楚地了解输入限制。

OpenHarmony确认弹窗实现

@CustomDialog
struct ConfirmDialog {
  controller: CustomDialogController
  title: string = ''
  content: string = ''
  cancelText: string = '取消'
  confirmText: string = '确定'
  onCancel: () => void = () => {}
  onConfirm: () => void = () => {}

  build() {
    Column() {
      Text(this.title)
        .fontSize(17)
        .fontWeight(FontWeight.Medium)
        .fontColor('#333333')
        .textAlign(TextAlign.Center)
      
      Text(this.content)
        .fontSize(14)
        .fontColor('#666666')
        .textAlign(TextAlign.Center)
        .margin({ top: 12 })
      
      this.Buttons()
    }
    .width(280)
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

OpenHarmony使用@CustomDialog装饰器定义自定义弹窗。controller是弹窗控制器,用于打开和关闭弹窗。Column垂直排列标题、内容和按钮。样式设置包括固定宽度、内边距、白色背景和圆角。这种实现方式与Flutter版本结构一致,确保两个平台的视觉效果统一。

按钮区域ArkUI实现:

@Builder
Buttons() {
  Row() {
    Button(this.cancelText)
      .layoutWeight(1)
      .height(44)
      .backgroundColor('#F5F5F5')
      .fontColor('#666666')
      .borderRadius(22)
      .onClick(() => {
        this.controller.close()
        this.onCancel()
      })
    
    Button(this.confirmText)
      .layoutWeight(1)
      .height(44)
      .backgroundColor('#E53935')
      .fontColor(Color.White)
      .borderRadius(22)
      .margin({ left: 12 })
      .onClick(() => {
        this.controller.close()
        this.onConfirm()
      })
  }
  .width('100%')
  .margin({ top: 20 })
}

@Builder装饰器定义了按钮区域的构建方法。Row水平排列取消和确认按钮,layoutWeight(1)使两个按钮平分宽度。Button组件设置高度、背景色、文字颜色和圆角。onClick事件先关闭弹窗再执行回调。这种实现方式与Flutter版本功能一致。

弹窗控制器使用

@Entry
@Component
struct DialogExample {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ConfirmDialog({
      title: '确认删除',
      content: '删除后无法恢复,确定要删除吗?',
      onConfirm: () => {
        // 执行删除操作
      }
    }),
    alignment: DialogAlignment.Center,
    autoCancel: false
  })

  build() {
    Button('显示弹窗')
      .onClick(() => {
        this.dialogController.open()
      })
  }
}

CustomDialogController用于控制弹窗的显示和隐藏。builder参数传入弹窗组件实例,alignment设置弹窗位置为居中,autoCancel设为false禁止点击遮罩关闭。调用open方法显示弹窗,close方法关闭弹窗。这种控制器模式使弹窗的管理更加灵活。

加载弹窗组件

class LoadingDialog extends StatelessWidget {
  final String? message;

  const LoadingDialog({
    Key? key,
    this.message,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return Dialog(
      backgroundColor: Colors.transparent,
      elevation: 0,
      child: Container(
        padding: const EdgeInsets.all(24),
        decoration: BoxDecoration(
          color: Colors.black.withOpacity(0.8),
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
            ),
            if (message != null) ...[
              const SizedBox(height: 16),
              Text(
                message!,
                style: const TextStyle(
                  fontSize: 14,
                  color: Colors.white,
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

LoadingDialog组件用于显示加载状态,阻止用户在加载过程中进行其他操作。CircularProgressIndicator显示旋转的加载指示器,白色配色在深色背景上清晰可见。message是可选的加载提示文字,如"正在提交…"。Dialog设置透明背景和零阴影,只显示中间的加载容器。这种设计简洁明了,让用户知道应用正在处理中。

总结

本文详细介绍了Flutter和OpenHarmony平台上弹窗对话框组件的开发过程。弹窗作为重要的交互组件,其设计质量直接影响用户的操作体验。通过确认弹窗、提示弹窗、输入弹窗、加载弹窗等组件的合理设计,我们为用户提供了清晰便捷的交互方式。在实际项目中,还可以进一步添加动画效果、手势关闭、多按钮支持等功能。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐