Flutter DatePicker实现基于选中日期的周/月间隔日期可选
Flutter 日期选择器固定间隔选日实现方案
需求说明
实现两级日期选择逻辑:先选定初始日期,第二个日期选择器仅允许选中与初始日期间隔为整周(7天倍数)或整月(30天/自然月偏移)的日期,原有实现逻辑不符合预期。
原有参考代码
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { DateTime selectedDate = DateTime(2020, 1, 14); // DatePicker will call this function on every day and expect // a bool output. If it's true, it will draw that day as "enabled" // and that day will be selectable and vice versa. bool _predicate(DateTime day) { print(day); if ((day.isAfter(selectedDate.subtract(Duration(days: 1))) && day.isBefore(selectedDate.add(Duration(days: 7))))) { return true; } return false; } Future<void> _selectDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, selectableDayPredicate: _predicate, firstDate: DateTime(2019), lastDate: DateTime(2021), builder: (context, child) { return Theme( data: ThemeData( primaryColor: Colors.orangeAccent, disabledColor: Colors.brown, textTheme: TextTheme(bodyText1: TextStyle(color: Colors.blueAccent)), accentColor: Colors.yellow), child: child!, ); }); if (picked != null && picked != selectedDate) setState(() { selectedDate = picked; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Text("${selectedDate.toLocal()}".split(' ')[0]), SizedBox( height: 20.0, ), RaisedButton( onPressed: () => _selectDate(context), child: Text('Select date'), ), ], ), ), ); } }
原有逻辑问题
原_predicate方法仅放开了初始日期前1天到初始日期后7天范围内的日期,完全没有实现「间隔为7天/30天整数倍」的判断规则,因此运行后只有初始日期后一周内的日期可选,不符合需求。
当前运行效果:
实现方案
核心逻辑
- 先将待判断日期和初始选中日期都归一化到当日0点,消除时分秒毫秒部分带来的天数差计算误差
- 计算两个日期的天数差值,根据规则判断是否允许选中:
- 周度规则:天数差为7的整数倍
- 月度规则:如果业务要求固定30天间隔,判断天数差为30的整数倍;如果要求自然月间隔,判断待选日期与初始日期日字段一致、月份差为正整数即可
- 可根据业务需求决定是否允许选择初始日期之前的日期
修正后的核心代码
首先添加日期归一化工具方法:
// 日期归一化:去除时分秒,仅保留年月日 DateTime _normalizeDate(DateTime date) { return DateTime(date.year, date.month, date.day); }
替换原有_predicate方法,以下为固定7/30天间隔、仅允许选择未来日期的实现:
bool _predicate(DateTime day) { final normalizedDay = _normalizeDate(day); final normalizedSelected = _normalizeDate(selectedDate); // 计算两个日期的天数差 final int dayDiff = normalizedDay.difference(normalizedSelected).inDays; // 过滤初始日期之前的日期,不需要可删除该行 if (dayDiff < 0) return false; // 周度间隔判断:天数差为7的整数倍 final bool isWeekValid = dayDiff % 7 == 0; // 固定30天月度间隔判断 final bool isMonthValid = dayDiff % 30 == 0; return isWeekValid || isMonthValid; }
如果需要实现自然月间隔(如1月14日对应2月14日、3月14日,不受大小月、2月天数影响),将月度判断逻辑替换为:
// 自然月间隔判断:日字段一致,月份差为正整数 final bool isNaturalMonthValid = normalizedDay.day == normalizedSelected.day && (normalizedDay.year * 12 + normalizedDay.month) > (normalizedSelected.year * 12 + normalizedSelected.month);
注意事项
- 传入
showDatePicker的initialDate必须在可选日期范围内,否则会抛出组件异常 - 自然月判断需要额外处理月末特殊场景,比如初始日期为1月31日时,2月没有31日,可根据业务规则将当月最后一天设为可选
- 如果需要支持选择过去的日期,去掉
dayDiff < 0的判断,对天数差取绝对值后再做取模运算即可
内容的提问来源于stack exchange,提问作者Sagar Acharya
相关产品推荐
相关产品推荐

