flutter 自定义中间弹窗

本文介绍了如何在Flutter中使用`GeneralDialog`函数创建具有自定义样式和回调的可dismissible对话框,包括标题、内容、按钮和主题色设置。
// An highlighted block


import 'package:flutter/material.dart';


class ShowGeneralDialogFunction {
  static void showGeneralDialogFunction(
      BuildContext context, {
      //取消回调
        Function? callBack,
        //确认回调
        Function? cancelBack,
        double? width,
        double? height,
        Widget? title,
        Widget? content,
        String? contentTitle,
        String? cancelText,
        String? disableText,
        Color? cancelTextColor,
        Color? disableTextColor,
        bool barrierDismissible = true,
        //按钮数量,可以设置为一个按钮
        int textButton = 2,
      }) {
    showDialog(
        context: context,
        barrierDismissible: barrierDismissible,
        builder: (buildContext) {
          return Material(
            type: MaterialType.transparency,
            child: Scaffold(
              backgroundColor: Colors.transparent,
              body: Center(
                child: Container(
                  width: width ?? 600.rpx,
                  clipBehavior: Clip.antiAlias,
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(24.rpx)),
                  // height: 300.rpx,

                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      title ?? Container(),
                      content ??
                          Container(
                            margin: EdgeInsets.only(
                                top: 40.rpx,
                                bottom: 40.rpx,
                                right: 24.rpx,
                                left: 24.rpx),
                            child: Text(
                              contentTitle ??
                               "contentTitle",
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                  height: 1.5,
                                  fontSize: 28.rpx,
                                  color: const Color(0xff2E3346),
                                  fontWeight: FontWeight.w400),
                            ),
                          ),
                      textButton == 2
                          ? Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          GestureDetector(
                            behavior: HitTestBehavior.opaque,
                            onTap: () {
                              if (cancelBack != null) cancelBack();
                              Navigator.pop(buildContext);
                            },
                            child: Container(
                              alignment: Alignment.center,
                              width: width == null ? 300.rpx : width / 2,
                              height: height ?? 100.rpx,
                              decoration: const BoxDecoration(
                                border: Border(
                                    top: BorderSide(
                                      width: 0.5, //宽度
                                      color: Color(0xffEEEEEE), //边框颜色
                                    )),
                              ),
                              child: Text(
                                cancelText ??
                                  "cancelText",
                                style: TextStyle(
                                    color: cancelTextColor ??
                                        const Color(0xffC8CCD7),
                                    fontSize: 36.rpx,
                                    fontWeight: FontWeight.w500),
                              ),
                            ),
                          ),
                          GestureDetector(
                            behavior: HitTestBehavior.opaque,
                            onTap: () {
                              if (callBack != null) callBack();
                              Navigator.pop(buildContext);
                            },
                            child: Container(
                              alignment: Alignment.center,
                              width: width == null ? 300.rpx : width / 2,
                              height: height ?? 100.rpx,
                              decoration: const BoxDecoration(
                                border: Border(
                                    left: BorderSide(
                                      width: 0.5, //宽度
                                      color: Color(0xffEEEEEE), //边框颜色
                                    ),
                                    top: BorderSide(
                                      width: 0.5, //宽度
                                      color: Color(0xffEEEEEE), //边框颜色
                                    )),
                              ),
                              child: Text(
                                disableText ??
                                    "disableText",
                                style: TextStyle(
                                    fontSize: 36.rpx,
                                    letterSpacing: -2.rpx,
                                    color: disableTextColor ??
                                        const Color(0xff1A71FF),
                                    fontWeight: FontWeight.w500),
                              ),
                            ),
                          )
                        ],
                      )
                          : Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          GestureDetector(
                            behavior: HitTestBehavior.opaque,
                            onTap: () {
                              if (callBack != null) callBack();
                              Navigator.pop(buildContext);
                            },
                            child: Container(
                              alignment: Alignment.center,
                              width: width ?? 600.rpx,
                              height: height ?? 100.rpx,
                              decoration: const BoxDecoration(
                                border: Border(
                                    top: BorderSide(
                                      width: 0.5, //宽度
                                      color: Color(0xffEEEEEE), //边框颜色
                                    )),
                              ),
                              child: Text(
                                disableText ??
                                 "disableText",
                                style: TextStyle(
                                    fontSize: 36.rpx,
                                    letterSpacing: -2.rpx,
                                    color: disableTextColor ??
                                        const Color(0xffE72B00),
                                    fontWeight: FontWeight.w500),
                              ),
                            ),
                          )
                        ],
                      )
                    ],
                  ),
                ),
              ),
            ),
          );
        });
  }
}
<think>我们使用Flutter的showModalBottomSheet来展示底部弹窗,并在其中使用CupertinoDatePicker(苹果风格的时间选择器)或自定义的滚动选择器。由于要求是月日时分,我们可以使用CupertinoDatePicker并设置模式为dateAndTime,然后自定义显示的格式?但是注意:CupertinoDatePicker默认显示的是年月日时分,而我们只需要月日时分。 不过,我们可以通过自定义选择器来实现。这里我们可以使用第三方库,比如`flutter_cupertino_date_picker`(但注意这个库可能不再维护)或者使用`flutter_picker`,或者自己用ListView构建。 但是,为了更符合要求,我们可以使用CupertinoDatePicker,然后通过设置最小和最大日期来限制年份,但是这样用户还是能看到年份,只是不能选择其他年份。这并不符合只显示月日时分的要求。 因此,我们可以考虑使用`flutter_picker`库,它允许我们自定义选择器的每一列。 但是,题目要求采用滚动的样式,我们可以自己构建一个滚动选择器,使用多个`ListView`(每个代表月、日、时、分)并排。 由于时间关系,这里我们使用`flutter_cupertino_date_picker`库已经不太合适(因为需要自定义格式),而使用`flutter_picker`可以轻松实现。但是,为了减少依赖,我们也可以自己实现。 考虑到实现复杂度,这里我们使用Flutter自带的CupertinoDatePicker,并设置模式为`dateAndTime`,然后通过设置最小和最大日期为同一年,这样年份就固定了,用户只能选择同一年内的日期,但是这样还是显示年份。所以,我们可以通过自定义一个Widget来覆盖年份显示,但这样比较麻烦。 另一种方案:使用`showModalBottomSheet`,里面放置一个自定义的Widget,由四个水平排列的ListView组成,分别代表月、日、时、分。 由于这是一个较为复杂的选择器,我们可能需要自己构建。但为了简化,我们使用一个折中方案:使用CupertinoDatePicker,但只使用时间选择器(time)和日期选择器(date)分开两次选择?但是题目要求是同时选择月日时分。 根据要求,我们决定自己构建一个自定义的滚动选择器。 步骤: 1. 创建StatefulWidget,用于展示底部弹窗。 2. 使用showModalBottomSheet展示这个Widget。 3. 在Widget中,我们使用Row包裹四个ListView,分别用于选择月、日、时、分。 4. 每个ListView都包含一个固定范围的数据(月:1-12,日:根据月份动态变化,时:0-23,分:0-59)。 5. 监听每个ListView的滚动,更新选中的值。 6. 添加确认和取消按钮。 但是,自己实现一个完整的滚动选择器比较复杂,尤其是需要平滑滚动和固定选择位置。因此,我们可以使用`ListWheelScrollView`,它提供了类似iOS的滚轮效果。 由于`ListWheelScrollView`需要固定高度,我们可以设置每个滚轮的高度。 具体实现: 我们将创建一个自定义弹窗Widget,它包含四个`ListWheelScrollView`,分别对应月、日、时、分。并且,我们将使用`FixedExtentScrollController`来控制每个滚轮的位置。 注意:日的范围需要根据月份和闰年情况调整(因为年份没有选择,我们默认使用当前年份?或者不涉及年份?题目要求是月日时分,所以年份可以忽略,但日需要根据月份来定,比如2月有28或29天,我们这里可以默认按闰年算,或者按28天?为了简单,我们可以固定为31天,然后在选择月份后动态改变日的天数。但是,如果用户选择2月,那么日应该只到28或29?这里我们可以固定为31天,然后当选择月份为2月时,日只有28天(不考虑闰年),或者29天?为了简单,我们不考虑闰年,2月固定为29天(这样闰年和非闰年都支持,因为用户选择2月29日时,非闰年可以忽略?但这样不严谨)。由于题目没有要求年份,所以我们可以忽略闰年,每个月都按最大天数来显示,但这样用户可能会选择不存在的日期(如2月30日)。所以,我们需要根据月份动态调整日的天数。 因此,我们需要在月份变化时,更新日的列表。 步骤: 1. 定义状态类,包含当前选择的月份、日、小时、分钟。 2. 根据月份计算当月的最大天数(注意:2月按29天处理,因为无法确定年份,我们按闰年处理,避免2月29日无法选择)。 3. 构建四个ListWheelScrollView,分别用于选择月、日、时、分。 4. 每个ListWheelScrollView的项数分别为:月(12)、日(根据月份变化)、时(24)、分(60)。 5. 当月份变化时,更新日的列表,并调整日选择器的位置(如果当前选择的日大于新的月份的最大天数,则设置为最大天数)。 6. 添加确认和取消按钮。 由于代码较长,我们只写一个简化的版本,不考虑闰年,2月固定29天(这样闰年可以选29,非闰年用户选了29我们也不管,因为题目没有要求年份)。 月份天数数组: List<int> daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 注意:我们使用`ListWheelScrollView`,它需要设置`itemExtent`(每个条目的高度)和`diameterRatio`(直径比,控制外观)。 另外,为了美观,我们可以在中间画一条指示线。 由于时间限制,我们这里只写一个基本可用的版本。 以下是代码示例:</think>在 Flutter 中实现底部弹窗式时间选择器(月日时分格式)并采用滚动样式,可通过 `showModalBottomSheet` 结合 `CupertinoDatePicker` 或自定义 `ListWheelScrollView` 实现。以下是完整代码示例: ```dart import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('时间选择器示例')), body: Center( child: ElevatedButton( onPressed: () => _showCustomPicker(context), child: const Text('打开时间选择器'), ), ), ), ); } void _showCustomPicker(BuildContext context) { DateTime now = DateTime.now(); // 初始化选中时间(当前时间) DateTime selectedDate = DateTime(now.year, now.month, now.day, now.hour, now.minute); // 月份列表(1-12) final months = List.generate(12, (index) => index + 1); // 日期列表(动态生成,根据月份变化) List<int> days = _generateDays(selectedDate.month); // 小时列表(0-23) final hours = List.generate(24, (index) => index); // 分钟列表(0-59) final minutes = List.generate(60, (index) => index); showModalBottomSheet( context: context, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return Container( padding: const EdgeInsets.all(16), height: 300, child: Column( children: [ // 标题和确认按钮 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), const Text('选择时间', style: TextStyle(fontSize: 18)), TextButton( onPressed: () { print('选中时间: ${_formatDate(selectedDate)}'); Navigator.pop(context, selectedDate); }, child: const Text('确认'), ), ], ), // 滚动选择器区域 Expanded( child: Row( children: [ // 月份选择 _buildWheelPicker( items: months, selectedIndex: selectedDate.month - 1, onChanged: (index) { setState(() { selectedDate = DateTime( selectedDate.year, months[index], selectedDate.day, selectedDate.hour, selectedDate.minute, ); days = _generateDays(selectedDate.month); }); }, ), // 日期选择 _buildWheelPicker( items: days, selectedIndex: selectedDate.day - 1, onChanged: (index) { setState(() { selectedDate = DateTime( selectedDate.year, selectedDate.month, days[index], selectedDate.hour, selectedDate.minute, ); }); }, ), // 小时选择 _buildWheelPicker( items: hours, selectedIndex: selectedDate.hour, onChanged: (index) { setState(() => selectedDate = DateTime( selectedDate.year, selectedDate.month, selectedDate.day, hours[index], selectedDate.minute, )); }, ), // 分钟选择 _buildWheelPicker( items: minutes, selectedIndex: selectedDate.minute, onChanged: (index) { setState(() => selectedDate = DateTime( selectedDate.year, selectedDate.month, selectedDate.day, selectedDate.hour, minutes[index], )); }, ), ], ), ), ], ), ); }, ); }, ); } // 构建单个滚轮选择器 Widget _buildWheelPicker({ required List<int> items, required int selectedIndex, required Function(int) onChanged, }) { return Expanded( child: ListWheelScrollView( itemExtent: 50, diameterRatio: 1.5, physics: const FixedExtentScrollPhysics(), onSelectedItemChanged: onChanged, childDelegate: ListWheelChildBuilderDelegate( childCount: items.length, builder: (context, index) { final isSelected = index == selectedIndex; return Center( child: Text( items[index].toString().padLeft(2, '0'), style: TextStyle( fontSize: isSelected ? 24 : 18, color: isSelected ? Colors.blue : Colors.grey, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ); }, ), ), ); } // 生成当月天数列表(自动处理闰年) List<int> _generateDays(int month) { final daysInMonth = DateUtils.getDaysInMonth(DateTime.now().year, month); return List.generate(daysInMonth, (index) => index + 1); } // 格式化日期为 "月日时分" 格式 String _formatDate(DateTime date) { return '${date.month}月${date.day}日 ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; } } ``` ### 关键实现说明: 1. **底部弹窗**:使用 `showModalBottomSheet` 创建底部弹出层 2. **滚动选择器**:通过 `ListWheelScrollView` 实现四列独立滚轮(月/日/时/分) 3. **动态日期生成**:`_generateDays()` 方法根据月份动态计算当月天数(自动处理闰年) 4. **交互反馈**: - 选中项放大加粗显示 - 月份变化时自动更新日期范围 - 双位数字补零显示(如 05 分) 5. **数据格式**:最终输出格式为 "月日时分"(示例:`5月15日 14:30`) ### 效果特点: - 四列独立滚轮横向排列 - 选中项高亮显示 - 平滑滚动物理效果 - 动态日期范围处理 - 顶部操作栏(取消/确认)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值