在Flutter 应用开发过程中,或多或少的都会涉及到时间选择器相关的内容。Flutter默认提供了DatePicker日期选择器,如果对样式没有特殊的要求,那么可以使用它来进行时间的选择,默认的样式如下所示。
使用示例代码如下:
代码语言:javascript复制import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:async';
class DateTimeDemo extends StatefulWidget {
@override
_DateTimeDemoState createState() => _DateTimeDemoState();
}
class _DateTimeDemoState extends State<DateTimeDemo> {
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay(hour: 9, minute: 30);
Future<void> _selectDate() async {
final DateTime date = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (date == null) return;
setState(() {
selectedDate = date;
});
}
Future<void> _selectTime() async {
final TimeOfDay time = await showTimePicker(
context: context,
initialTime: selectedTime,
);
if (time == null) return;
setState(() {
selectedTime = time;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DateTimeDemo'),
elevation: 0.0,
),
body: Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
onTap: _selectDate,
child: Row(
children: <Widget>[
Text(DateFormat.yMMMMd().format(selectedDate)),
Icon(Icons.arrow_drop_down),
],
),
),
InkWell(
onTap: _selectTime,
child: Row(
children: <Widget>[
Text(selectedTime.format(context)),
Icon(Icons.arrow_drop_down),
],
),
),
],
),
],
),
)
);
}
}
可以发现,默认的样式并不是很友好。通常在移动应用开发中,App的涉及多是参考iOS的设计来的,所以这时候,多半需要进行自定义组件了。不管,为了快速的进行开发我们可以选择一些第三方的组件库,如flutter_custom_calendar,此库具有如下的功能:
- 支持公历,农历,节气,传统节日,常用节假日
- 日期范围设置,默认支持的最大日期范围为1971.01-2055.12
- 禁用日期范围设置,比如想实现某范围的日期内可以点击,范围外的日期置灰
- 支持单选、多选模式,提供多选超过限制个数的回调和多选超过指定范围的回调。
- 跳转到指定日期,默认支持动画切换
- 自定义日历Item,支持组合widget的方式和利用canvas绘制的方式
- 自定义顶部的WeekBar
- 根据实际场景,可以给Item添加自定义的额外数据,实现各种额外的功能。比如实- 现进度条风格的日历,实现日历的各种标记
- 支持周视图的展示,支持月份视图和星期视图的展示与切换联动
如下是部分效果图:
实际使用时,我们需要根据样式对该库进行二次开发,首先,新建一个date_picker_widget.dart文件,然后添加如下代码:
代码语言:javascript复制import 'package:flutter/material.dart';
import 'package:flutter_custom_calendar/flutter_custom_calendar.dart';
class DatePickerWidget extends StatefulWidget {
final ValueSetter<String> onSetter;
DatePickerWidget({@required this.onSetter});
@override
_DatePickerWidgetState createState() => _DatePickerWidgetState();
}
class _DatePickerWidgetState extends State<DatePickerWidget> {
ValueNotifier<String> text;
ValueNotifier<String> selectText;
CalendarController controller ;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(icon: Icon(Icons.navigate_before), onPressed: (){
controller.moveToPreviousMonth();
}),
ValueListenableBuilder(
valueListenable: text,
builder: (context, value, child) {
return Text(text.value);
}),
IconButton(
icon: Icon(Icons.navigate_next),
onPressed: () {
controller.moveToNextMonth();
}),
],
),
),
CalendarViewWidget(
boxDecoration: BoxDecoration(
color: Colors.white
),
calendarController: controller,
weekBarItemWidgetBuilder: () {
return CustomStyleWeekBarItem();
},
dayWidgetBuilder: (dateModel) {
return CustomStyleDayWidget(dateModel);
},
),
],
);
}
List<DateTime> getHighlightedDates() {
return List<DateTime>.generate(10,
(int index) => DateTime.now().add(Duration(days: 10 * (index 1))),
);
}
int maxSelectYear() {
return DateTime.now().year;
}
int maxSelectMounth() {
return DateTime.now().month;
}
int maxSelectDay() {
return DateTime.now().day;
}
@override
void initState() {
super.initState();
controller = CalendarController(
maxSelectYear: maxSelectYear(),
maxSelectMonth: maxSelectMounth(),
maxSelectDay:maxSelectDay(),
);
controller.addMonthChangeListener(
(year, month) {
text.value = "$year年$month月";
},
);
controller.addOnCalendarSelectListener((dateModel){
String month = dateModel.month < 10 ? '0${dateModel.month}': '${dateModel.month}';
String day = dateModel.day < 10 ? '0${dateModel.day}': '${dateModel.day}';
this.widget.onSetter('${dateModel.year}-${month}-${day}');
});
// controller.addOnCalendarSelectListener((dateModel) {
// debugPrint(dateModel.toString() ' ');
// //刷新选择的时间
// selectText.value =
// "单选模式n选中的时间:n${controller.getSingleSelectCalendar()}";
// });
//
text = new ValueNotifier("${DateTime.now().year}年${DateTime.now().month}月");
//
// selectText = new ValueNotifier(
// "单选模式n选中的时间:n${controller.getSingleSelectCalendar()}");
}
}
class CustomStyleWeekBarItem extends BaseWeekBar {
final List<String> weekList = ["日","一", "二", "三", "四", "五", "六",];
@override
Widget getWeekBarItem(int index) {
return Container(
child: Center(
child: Text(weekList[index]),
),
);
}
}
class CustomStyleDayWidget extends BaseCustomDayWidget {
CustomStyleDayWidget(DateModel dateModel) : super(dateModel);
@override
void drawNormal(DateModel dateModel, Canvas canvas, Size size) {
if (!dateModel.isCurrentMonth) {
return;
}
bool isWeekend = dateModel.isWeekend;
bool isInRange = dateModel.isInRange;
//顶部的文字
TextPainter dayTextPainter = new TextPainter()
..text = TextSpan(
text: dateModel.day.toString(),
style: new TextStyle(
color: !isInRange
? Colors.grey
: isWeekend ? Colors.blue : Colors.black,
fontSize: 16))
..textDirection = TextDirection.ltr
..textAlign = TextAlign.center;
dayTextPainter.layout(minWidth: size.width, maxWidth: size.width);
dayTextPainter.paint(canvas, Offset(0, 10));
//下面的文字
TextPainter lunarTextPainter = new TextPainter()
..text = new TextSpan(
text: dateModel.lunarString,
style: new TextStyle(
color: !isInRange
? Colors.grey
: isWeekend ? Colors.blue : Colors.grey,
fontSize: 12))
..textDirection = TextDirection.ltr
..textAlign = TextAlign.center;
lunarTextPainter.layout(minWidth: size.width, maxWidth: size.width);
lunarTextPainter.paint(canvas, Offset(0, size.height / 2));
}
@override
void drawSelected(DateModel dateModel, Canvas canvas, Size size) {
if (!dateModel.isCurrentMonth) {
return;
}
//绘制背景
Paint backGroundPaint = new Paint()
..color = Colors.blue
..strokeWidth = 2;
double padding = 8;
canvas.drawCircle(Offset(size.width / 2, size.height / 2),
(size.width - padding) / 2, backGroundPaint);
//顶部的文字
TextPainter dayTextPainter = new TextPainter()
..text = TextSpan(
text: dateModel.day.toString(),
style: new TextStyle(color: Colors.white, fontSize: 16))
..textDirection = TextDirection.ltr
..textAlign = TextAlign.center;
dayTextPainter.layout(minWidth: size.width, maxWidth: size.width);
dayTextPainter.paint(canvas, Offset(0, 10));
//下面的文字
TextPainter lunarTextPainter = new TextPainter()
..text = new TextSpan(
text: dateModel.lunarString,
style: new TextStyle(color: Colors.white, fontSize: 12))
..textDirection = TextDirection.ltr
..textAlign = TextAlign.center;
lunarTextPainter.layout(minWidth: size.width, maxWidth: size.width);
lunarTextPainter.paint(canvas, Offset(0, size.height / 2));
}
}
然后,我们新建一个date_picker_dialog.dart的Dialog自定义组件,代码如下:
代码语言:javascript复制import 'package:flutter/material.dart';
import 'package:gc_data_app/pages/views/gaps.dart';
import 'package:gc_data_app/res/colors.dart';
import 'package:gc_data_app/routes/app_route.dart';
import 'package:gc_data_app/utils/date_utils.dart';
import 'package:gc_data_app/widgets/date_picker_new_widget.dart';
import 'package:gc_data_app/widgets/date_picker_widget.dart';
import 'package:gc_data_app/widgets/drop_down_widget.dart';
import 'package:toast/toast.dart';
class DatePickerDialog extends StatefulWidget {
const DatePickerDialog({
Key key,
@required this.onSelectedDate,
}) : super(key: key);
final Function(String) onSelectedDate;
@override
State<StatefulWidget> createState() {
return CustomTimeState();
}
}
class CustomTimeState extends State<DatePickerDialog> {
bool showDate = false;
var chooseDateStr = '';
@override
Widget build(BuildContext context) {
return Material(
child: SizedBox(
height: 475,
child: Container(
color: Colors.white,
child: Column(
children: <Widget>[
_buildTitleBar(),
_buildLine(),
_buildDatePicker(),
],
),
),
),
);
}
Widget _buildTitleBar() {
return Container(
padding: EdgeInsets.only(top: 10, left: 15, bottom: 10, right: 15),
child: Row(
children: <Widget>[
InkWell(
child: Text(
'取消',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w300,
color: ColorRes.text_little_blue),
),
onTap: () {
AppRoute.pop(context);
},
),
Expanded(
child: Center(
child: Text(
'选择日期',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w300),
),
),
),
InkWell(
child: Text('确认',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w300,
color: ColorRes.text_little_blue)),
onTap: () {
if(chooseDateStr==''){
Toast.show('选择日期不能为空!', context);
return ;
}
DateTime choose= DateUtils.strToDate(chooseDateStr);
DateTime limitStart= DateUtils.strToDate('2014-01-01');
if(choose.isBefore(limitStart)){
Toast.show('起始日期不能早于2014-01-01', context);
return ;
}
widget.onSelectedDate(chooseDateStr);
FocusScope.of(context).unfocus();
Navigator.pop(context);
},
)
],
),
);
}
Widget _buildLine() {
return Gaps.line;
}
Widget _buildDatePicker() {
return DatePickerWidget(onSetter: (value) {
showDate = false;
chooseDateStr = value;
});
}
}
实际使用时候,使用showCupertinoModalPopup组件展示出来即可,如下所示。
代码语言:javascript复制showCalendarSheet(){
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) {
return DatePickerDialog(onSelectedDate: (value){
},
);
},
);
}