Flutter---自定义日期选择对话框
效果图

功能概述
-
页面背景是蓝白渐变色;
-
页面上有一个按钮,点击后弹出一个自定义日期选择对话框;
-
弹窗里使用了 三个
CupertinoPicker分别选择“年 / 月 / 日”; -
选择完后点击“确定”,日期会显示在主页上;
-
点击“取消”会关闭弹窗,不做修改。
实现步骤
1.定义一个生日日期变量
var _birthday = "1900-01-01"; //1.设置变量并赋默认值
2.构建主题UI
return Scaffold(//2.构建UIbody: Container(height: double.infinity,//覆盖高width: double.infinity,//覆盖宽padding: const EdgeInsets.symmetric(vertical: 10,horizontal:10),//边内边距//主页背景颜色上下渐变decoration: const BoxDecoration(gradient: LinearGradient(begin: Alignment.topCenter,end: Alignment.centerRight,colors: [Colors.blue,Colors.white,],),),child: GestureDetector(onTap:changeBirthdayDialog ,//设置按钮的点击事件child: Column(children: [SizedBox(height: 100,),//按钮Container(//按钮大小height: 50,width: 200,//按钮的样式decoration: BoxDecoration(color: Colors.white,borderRadius: BorderRadius.circular(20),),child: Center(child: Text("选择生日的按钮"),)),SizedBox(height: 20,),//日期的显示Row(mainAxisAlignment: MainAxisAlignment.center,children: [Text("您的生日是 :"),Text(_birthday)],)],))),);
3.实现日期选择对话框:定义一些初始值
//1.设定初始值int selectedYear = 1900; //默认选中年份int selectedMonth = 1; //默认选中月份int selectedDay = 1; //默认选中日期//2.设置年的初始选中项为2000final controllerYear = FixedExtentScrollController(initialItem: 100);
4.构建年份选择(请重点理解CupertinoPicker的相关属性)
// 年份选择Expanded(child: Column(children: [Text('年', style: TextStyle(fontSize: 14, color: Colors.grey)), //顶部的标识SizedBox(height: 120,child: Stack(children: [CupertinoPicker(itemExtent: 40, //选中项的高度scrollController: controllerYear,//设置初始选中项的值onSelectedItemChanged: (int index) { //当选中项变化时触发,参数为索引selectedYear = 1900 + index; //存储用户选择的数据(将数组索引转换为实际的年份值)},// 移除选择覆盖层selectionOverlay: null,//DateTime.now().year获取当年年份//List.generate(126,(index)):生成指定长度的列表children: List.generate(DateTime.now().year - 1899, (index) {return Center(child: Text('${1900 + index}', //年滑动框显示的文本style: TextStyle(fontSize: 18),//文本的样式),);}),),// 上横线Positioned(top: 40, // 调整到合适位置left: 0,right: 0,child: Container(height: 1,color: Color(0xFFD8D8D8).withOpacity(0.6),),),// 下横线Positioned(bottom: 40, // 调整到合适位置left: 0,right: 0,child: Container(height: 1,color: Color(0xFFD8D8D8).withOpacity(0.6),),),],),),],),),
5.相同手法构建月和日
// 月份选择Expanded(child: Column(children: [Text('月', style: TextStyle(fontSize: 14, color: Colors.grey)),SizedBox(height: 120,child: Stack(children: [CupertinoPicker(itemExtent: 40,onSelectedItemChanged: (int index) {selectedMonth = index + 1;},// 移除选择覆盖层selectionOverlay: null,children: List.generate(12, (index) {return Center(child: Text('${index + 1}',//月滑动框显示的文本style: TextStyle(fontSize: 18),),);}),),Positioned(top: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(bottom: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),],),),],),),// 日期选择Expanded(child: Column(children: [Text('日', style: TextStyle(fontSize: 14, color: Colors.grey)),SizedBox(height: 120,child: Stack(children: [CupertinoPicker(itemExtent: 40,onSelectedItemChanged: (int index) {selectedDay = index + 1;},// 移除选择覆盖层selectionOverlay: null,children: List.generate(31, (index) {return Center(child: Text('${index + 1}',//日滑动框显示的文本style: TextStyle(fontSize: 18),),);}),),Positioned(top: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(bottom: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),],),),],),),],),
6.构建两个按钮,以及确定按钮背后的实现的逻辑
// 确认按钮Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [TextButton(style: TextButton.styleFrom(backgroundColor: Color(0xFFD8D8D8),foregroundColor: Color(0xFF3D3D3D),minimumSize: Size(100, 40),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20),),),onPressed: () => Navigator.pop(context),child: const Text('取消',style: TextStyle(fontSize: 16),),),TextButton(style: TextButton.styleFrom(backgroundColor: Color(0xFF1F8FFF),foregroundColor: Colors.white,minimumSize: Size(100, 40),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20),),),onPressed: () {///将年、月、日组合成标准格式的日期字符串String formattedDate = "$selectedYear-${selectedMonth.toString().padLeft(2, '0')}-${selectedDay.toString().padLeft(2, '0')}";setState(() {_birthday = formattedDate;});Navigator.pop(context);},child: const Text('确定',style: TextStyle(fontSize: 16),),),],),
7.确认按钮的实现逻辑
// 假设当前选中的是:selectedYear = 2024, selectedMonth = 5, selectedDay = 8// 1. 年份部分
"$selectedYear" → "2024"// 2. 月份部分(UI确保要两位数)
selectedMonth.toString() → "5"(字符串)
.padLeft(2, '0') → "05"(左侧补零到2位)// 3. 日期部分(UI确保要两位数)
selectedDay.toString() → "8"(字符串)
.padLeft(2, '0') → "08"(左侧补零到2位)// 4. 最终组合
"2024-05-08"//5.把这个值赋值给变量_brithday,变量发生改变,UI重建
完整数据流程图
用户点击按钮↓
打开生日选择对话框↓
用户滑动选择器 → 更新局部变量 (selectedYear/Month/Day)↓
用户点击"确定"按钮↓
格式化日期字符串 "YYYY-MM-DD"↓
setState() 更新 _birthday 状态↓
页面自动重新构建 (rebuild)↓
UI 显示新的生日
如果你不喜欢年月日在最上面,想要年月日在选中区,效果图类似这样

可以把最上面实现的代码行注释掉。在两条横线中间加上年月日,以下是年的实现代码,月和日也是相同的实现方法。
// 上横线Positioned(top: 40, // 调整到合适位置left: 0,right: 0,child: Container(height: 1,color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(top: 48, // 调整到合适位置left: 70,// 调整到合适位置right: 0,child: Container(child: Text("年",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),//color: Color(0xFFD8D8D8).withOpacity(0.6),),),// 下横线Positioned(bottom: 40, // 调整到合适位置left: 0,right: 0,child: Container(height: 1,color: Color(0xFFD8D8D8).withOpacity(0.6),),),
代码实例
home_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_flutter/person_information_page.dart';class HomePage extends StatefulWidget{const HomePage({super.key});@overrideState<StatefulWidget> createState() => _HomePageState();}class _HomePageState extends State<HomePage> {var _birthday = "1900-01-01"; //1.设置变量并赋默认值@overrideWidget build(BuildContext context) {return Scaffold(//2.构建UIbody: Container(height: double.infinity,//覆盖高width: double.infinity,//覆盖宽padding: const EdgeInsets.symmetric(vertical: 10,horizontal:10),//边内边距//主页背景颜色上下渐变decoration: const BoxDecoration(gradient: LinearGradient(begin: Alignment.topCenter,end: Alignment.centerRight,colors: [Colors.blue,Colors.white,],),),child: GestureDetector(onTap:changeBirthdayDialog ,//设置按钮的点击事件child: Column(children: [SizedBox(height: 100,),//按钮Container(//按钮大小height: 50,width: 200,//按钮的样式decoration: BoxDecoration(color: Colors.white,borderRadius: BorderRadius.circular(20),),child: Center(child: Text("选择生日的按钮"),)),SizedBox(height: 20,),//日期的显示Row(mainAxisAlignment: MainAxisAlignment.center,children: [Text("您的生日是 :"),Text(_birthday)],)],))),);}//3.构建选择生日的弹窗void changeBirthdayDialog() {//1.设定初始值int selectedYear = 1900; //默认选中年份int selectedMonth = 1; //默认选中月份int selectedDay = 1; //默认选中日期//2.设置年的初始选中项为2000final controllerYear = FixedExtentScrollController(initialItem: 100);showDialog(context: context,builder: (context) {return Dialog(backgroundColor: Colors.white,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(26),),child: Container(width: 320,padding: const EdgeInsets.all(16),child: Column(mainAxisSize: MainAxisSize.min,children: [const SizedBox(height: 16),// 自定义年月日选择器Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [// 年份选择Expanded(child: Column(children: [Text('年', style: TextStyle(fontSize: 14, color: Colors.grey)), //顶部的标识SizedBox(height: 120,child: Stack(children: [CupertinoPicker(itemExtent: 40, //选中项的高度scrollController: controllerYear,//设置初始选中项的值onSelectedItemChanged: (int index) { //当选中项变化时触发,参数为索引selectedYear = 1900 + index; //存储用户选择的数据(将数组索引转换为实际的年份值)},// 移除选择覆盖层selectionOverlay: null,//DateTime.now().year获取当年年份//List.generate(126,(index)):生成指定长度的列表children: List.generate(DateTime.now().year - 1899, (index) {return Center(child: Text('${1900 + index}', //年滑动框显示的文本style: TextStyle(fontSize: 18),//文本的样式),);}),),// 上横线Positioned(top: 40, // 调整到合适位置left: 0,right: 0,child: Container(height: 1,color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(top: 48, // 调整到合适位置left: 70,// 调整到合适位置right: 0,child: Container(child: Text("年",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),//color: Color(0xFFD8D8D8).withOpacity(0.6),),),// 下横线Positioned(bottom: 40, // 调整到合适位置left: 0,right: 0,child: Container(height: 1,color: Color(0xFFD8D8D8).withOpacity(0.6),),),],),),],),),// 月份选择Expanded(child: Column(children: [Text('月', style: TextStyle(fontSize: 14, color: Colors.grey)),SizedBox(height: 120,child: Stack(children: [CupertinoPicker(itemExtent: 40,onSelectedItemChanged: (int index) {selectedMonth = index + 1;},// 移除选择覆盖层selectionOverlay: null,children: List.generate(12, (index) {return Center(child: Text('${index + 1}',//月滑动框显示的文本style: TextStyle(fontSize: 18),),);}),),Positioned(top: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(top: 48, // 调整到合适位置left: 60,right: 0,child: Container(child: Text("月",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),//color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(bottom: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),],),),],),),// 日期选择Expanded(child: Column(children: [Text('日', style: TextStyle(fontSize: 14, color: Colors.grey)),SizedBox(height: 120,child: Stack(children: [CupertinoPicker(itemExtent: 40,onSelectedItemChanged: (int index) {selectedDay = index + 1;},// 移除选择覆盖层selectionOverlay: null,children: List.generate(31, (index) {return Center(child: Text('${index + 1}',//日滑动框显示的文本style: TextStyle(fontSize: 18),),);}),),Positioned(top: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(top: 48, // 调整到合适位置left: 60,right: 0,child: Container(child: Text("日",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),//color: Color(0xFFD8D8D8).withOpacity(0.6),),),Positioned(bottom: 40,left: 0,right: 0,child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),),],),),],),),],),const SizedBox(height: 16),// 确认按钮Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [TextButton(style: TextButton.styleFrom(backgroundColor: Color(0xFFD8D8D8),foregroundColor: Color(0xFF3D3D3D),minimumSize: Size(100, 40),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20),),),onPressed: () => Navigator.pop(context),child: const Text('取消',style: TextStyle(fontSize: 16),),),TextButton(style: TextButton.styleFrom(backgroundColor: Color(0xFF1F8FFF),foregroundColor: Colors.white,minimumSize: Size(100, 40),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20),),),onPressed: () {//将年、月、日组合成标准格式的日期字符串String formattedDate = "$selectedYear-${selectedMonth.toString().padLeft(2, '0')}-${selectedDay.toString().padLeft(2, '0')}";setState(() {_birthday = formattedDate;});Navigator.pop(context);},child: const Text('确定',style: TextStyle(fontSize: 16),),),],),],),),);},);}}
