Flutter 自定义日历【Flutter 专题 11】
- 2021 年 11 月 28 日
本文字数:16783 字
阅读完需:约 55 分钟
我们使用的日历多年来一直在发展。从手写日历到印刷日历,我们现在手上都有一个数字日历,它是高度可定制的,并在我们需要提醒的准确时刻提醒我们我们的事件。
我们将看到如何在 Flutter 中构建和自定义日历小部件,以便为我们的用户提供这种体验。
尽管 Flutter 以日期和时间选择器的形式提供了一个日历小部件,它提供了可自定义的颜色、字体和用法,但它缺少一些功能。您可以使用它来选择日期和时间(或两者)并将其添加到您的应用程序中,但它需要与一个按钮和一个占位符相结合,可以保存选择的日期或时间。
所以,我将从 Flutter 架构提供的原生日历开始,然后转到 pub.dev 上最流行的日历小部件TableCalendar
。您还可以使用许多其他流行的日历小部件,但在本教程中,我们将深入介绍。
Flutter 日历小部件
TableCalendar ()
Flutter 日历小部件(日期选择器和时间选择器)
为了更彻底地解释这个小部件,
首先,让我们回顾一下showDatePicker
默认构造函数:
showDatePicker({
// it requires a context
required BuildContext context,
// when datePicker is displayed, it will show month of the current date
required DateTime initialDate,
// earliest possible date to be displayed (eg: 2000)
required DateTime firstDate,
// latest allowed date to be displayed (eg: 2050)
required DateTime lastDate,
// it represents TODAY and it will be highlighted
DateTime? currentDate,
// either by input or selected, defaults to calendar mode.
DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar or input,
// restricts user to select date from range to dates.
SelectableDayPredicate? selectableDayPredicate,
// text that is displayed at the top of the datePicker
String? helpText,
// text that is displayed on cancel button
String? cancelText,
// text that is displayed on confirm button
String? confirmText,
// use builder function to customise the datePicker
TransitionBuilder? Builder,
// option to display datePicker in year or day mode. Defaults to day
DatePickerMode initialDatePickerMode = DatePickerMode.day or year,
// error message displayed when user hasn't entered date in proper format
String? errorFormatText,
// error message displayed when date is not selectable
String? errorInvalidText,
// hint message displayed to prompt user to enter date according to the format mentioned (eg: dd/mm/yyyy)
String? fieldHintText,
// label message displayed for what the user is entering date for (eg: birthdate)
String? fieldLabelText,
})
关于上面的默认构造函数,大家可以参考下图,我已经指出了一些重要的属性,可以根据自己的需要进行自定义。
它是如何工作的?
下面我会先给答案加整个代码,以作展示,用户可以输入会议名称和链接,然后选择日期和时间。
整个代码
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: FlutterDatePickerExample());
}
}
class FlutterDatePickerExample extends StatelessWidget {
final ValueNotifier<DateTime?> dateSub = ValueNotifier(null);
final ValueNotifier<DateTime?> longDateSub = ValueNotifier(null);
final ValueNotifier<TimeOfDay?> timeSub = ValueNotifier(null);
final ValueNotifier<TimeOfDay?> timeSubShort = ValueNotifier(null);
final TextEditingController meetingName = TextEditingController();
final TextEditingController meetingLink = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Vanilla Calendar Flutter'),
),
body: Padding(
padding: const EdgeInsets.all(14.0),
child: SingleChildScrollView(
child:Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
' 创建会议',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24.0),
),
const SizedBox(
height: 20,
),
buildTextField(controller: meetingName, hint: '输入会议名称'),
const SizedBox(
height: 20,
),
buildTextField(controller: meetingLink, hint: '输入会议链接'),
const SizedBox(
height: 10,
),
const Text(
' Short Date',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 18.0),
),
ValueListenableBuilder<DateTime?>(
valueListenable: dateSub,
builder: (context, dateVal, child) {
return InkWell(
onTap: () async {
DateTime? date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2050),
currentDate: DateTime.now(),
initialEntryMode: DatePickerEntryMode.calendar,
initialDatePickerMode: DatePickerMode.day,
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(
primary: Colors.blueGrey,
onSurface: AppColors.blackCoffee,
)
),
child: child!,
);
});
dateSub.value = date;
},
child: buildDateTimePicker(
dateVal != null ? convertDate(dateVal) : ''));
}),
const SizedBox(
height: 10,
),
const Text(
' 12H Format Time',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 18.0),
),
ValueListenableBuilder<TimeOfDay?>(
valueListenable: timeSubShort,
builder: (context, timeVal, child) {
return InkWell(
onTap: () async {
TimeOfDay? time = await showTimePicker(
context: context,
builder: (context, child) {
return Theme(
data: Theme.of(context)
child: child!,
);
},
initialTime: TimeOfDay.now(),
);
timeSubShort.value = time;
},
child: buildDateTimePicker(timeVal != null
? convertTime(timeVal)
: ''));
}),
const SizedBox(
height: 20.0,
),
const Text(
' Long Date',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 18.0),
),
ValueListenableBuilder<DateTime?>(
valueListenable: longDateSub,
builder: (context, dateVal, child) {
return InkWell(
onTap: () async {
DateTime? date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2050),
builder: (context, child) {
return Theme(
data: Theme.of(context),
child: child!,
);
});
longDateSub.value = date;
},
child: buildDateTimePicker(
dateVal != null ? longDate(dateVal) : ''));
}),
const SizedBox(
height: 10,
),
const Text(
' 24H Format Time',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 18.0),
),
ValueListenableBuilder<TimeOfDay?>(
valueListenable: timeSub,
builder: (context, timeVal, child) {
return InkWell(
onTap: () async {
TimeOfDay? time = await showTimePicker(
context: context,
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: true),
child: child!,
);
},
initialTime: TimeOfDay.now(),
);
timeSub.value = time;
},
child: buildDateTimePicker(timeVal != null
? timeVal.format(context)
: ''));
}),
const SizedBox(height: 20.0,),
ElevatedButton(onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('会议创建成功'),
duration: Duration(seconds: 5),));
}, child: const Text('提交')),
],
),
),
),
);
}
String convertDate(DateTime dateTime) {
return DateFormat('yyyy年MM月dd日').format(dateTime);
}
String longDate(DateTime dateTime) {
return DateFormat('EEE, MMM d, yyy').format(dateTime);
}
String convertTime(TimeOfDay timeOfDay) {
DateTime tempDate = DateFormat('hh:mm').parse(
timeOfDay.hour.toString() + ':' + timeOfDay.minute.toString());
var dateFormat = DateFormat('h:mm a');
return dateFormat.format(tempDate);
}
Widget buildDateTimePicker(String data) {
return ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
side: const BorderSide(color: AppColors.eggPlant, width: 1.5),
),
title: Text(data),
trailing: const Icon(
Icons.calendar_today,
color: AppColors.eggPlant,
),
);
}
Widget buildTextField(
{String? hint, required TextEditingController controller}) {
return TextField(
controller: controller,
textCapitalization: TextCapitalization.words,
decoration: InputDecoration(
labelText: hint ?? '',
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.eggPlant, width: 1.5),
borderRadius: BorderRadius.circular(
10.0,
),
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.eggPlant, width: 1.5),
borderRadius: BorderRadius.circular(
10.0,
),
),
),
);
}
}
class AppColors {
AppColors._();
static const Color blackCoffee = Color(0xFF352d39);
static const Color eggPlant = Color(0xFF6d435a);
static const Color celeste = Color(0xFFb1ede8);
static const Color babyPowder = Color(0xFFFFFcF9);
static const Color ultraRed = Color(0xFFFF6978);
}
第 1 步:实施 ValueNotifier
我已经实现了一个ValueNotifier
将在文本字段中保存日期的方法。
final ValueNotifier<DateTime?> dateSub = ValueNotifier(null);
第 2 步:创建datePicker
对话框
随着ValueListenerBuilder
和实例DateTime
,并与 InkWell小部件,当我们点击
textField,一个
datePicker对话框会弹出。当用户点击所需的日期时,它将显示在
textField`:
ValueListenableBuilder<DateTime?>(
valueListenable: dateSub,
builder: (context, dateVal, child) {
return InkWell(
onTap: () async {
DateTime? date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2050),
currentDate: DateTime.now(),
initialEntryMode: DatePickerEntryMode.calendar,
initialDatePickerMode: DatePickerMode.day,
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.blueGrey,
accentColor: AppColors.blackCoffee,
backgroundColor: Colors.lightBlue,
cardColor: Colors.white,
)
),
child: child!,
);
});
dateSub.value = date;
},
child: buildDateTimePicker(
dateVal != null ? convertDate(dateVal) : ''));
}),
buildDateTimePicker
只不过是一个listTile
带有自定义边框和日历图标:
Widget buildDateTimePicker(String data) {
return ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
side: const BorderSide(color: AppColors.eggPlant, width: 1.5),
),
title: Text(data),
trailing: const Icon(
Icons.calendar_today,
color: AppColors.eggPlant,
),
);
}
我们还有一个字符串方法可以将日期转换为所需的格式:
String convertDate(DateTime dateTime) {
return DateFormat('dd/MM/yyyy').format(dateTime);
}
这是代码实现时的样子:
现在,让我们回到TableCalendar
我之前讨论过的,我们将如何实现它,以及我们将如何定制它以满足应用程序的需求。
有几种自定义可能性,讨论所有这些都超出了本文的范围。因此,我会尽量做到具体,只讨论其中最重要的部分。当然,也有我亲自试验过的代码实现,以及可供参考的图像。
表日历
安装非常简单:您需要从这里复制并粘贴pubspec.yaml
文件中的依赖项table_calendar
。
最新版本是:
table_calendar: ^3.0.2
现在,我将把它的构造函数分成三个部分:
设置
TableCalendar
小部件根据您的应用程序需求设计日历样式
将事件添加到日历
这样您就可以轻松理解代码并知道如何成功实现它。
第 1 步:设置TableCalendar
小部件
我已用SingleChildScrollView
做我的父小部件,然后在Card
小部件中添加了一个Column
小部件,以稍微提升日历。然后,我在TableCalendar
小部件中添加了小Card
部件作为其子部件:
SingleChildScrollView(
child: Column(
children: [
Card(
margin: const EdgeInsets.all(8.0),
elevation: 5.0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
side: BorderSide( color: AppColors.blackCoffee, width: 2.0),
),
child: TableCalendar(
// today's date
focusedDay: _focusedCalendarDate,
// earliest possible date
firstDay: _initialCalendarDate,
// latest allowed date
lastDay: _lastCalendarDate,
// default view when displayed
calendarFormat: CalendarFormat.month,
// default is Saturday & Sunday but can be set to any day.
// instead of day, a number can be mentioned as well.
weekendDays: const [DateTime.sunday, 6],
// default is Sunday but can be changed according to locale
startingDayOfWeek: StartingDayOfWeek.monday,
// height between the day row and 1st date row, default is 16.0
daysOfWeekHeight: 40.0,
// height between the date rows, default is 52.0
rowHeight: 60.0,
上面的代码正在使用一些默认值和一些根据区域设置进行自定义来设置将在移动屏幕上显示的日历。我在每个属性之前添加了注释以了解它的作用。
我知道TableCalendar
小部件的类文件中已经给出了解释,但有时用更简单的术语更容易理解属性。我习惯于阅读所有内容,理解它,然后我尝试为我的读者简化,以便他们在实现代码之前不必遍历每一行。
第 2 步: TableCalendar
的样式
好的,所以还有 3 个部分来设计表格日历。首先是标题,我们有月份的名称和一个按钮,可以在周视图和月视图之间切换。左右箭头在月份之间滚动。
根据应用程序的主题,您可以自定义所有内容,以便日历的外观和感觉,基本上是日历的整个 UI,与您的应用程序的 UI 相匹配。
再次将代码拆分为 3 部分:
headerStyle
// 日历标题样式
headerStyle: const HeaderStyle(
titleTextStyle:
TextStyle(color: AppColors.babyPowder, fontSize: 20.0),
decoration: BoxDecoration(
color: AppColors.eggPlant,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10))),
formatButtonTextStyle:
TextStyle(color: AppColors.ultraRed, fontSize: 16.0),
formatButtonDecoration: BoxDecoration(
color: AppColors.babyPowder,
borderRadius: BorderRadius.all(
Radius.circular(5.0),
), ),
leftChevronIcon: Icon(
Icons.chevron_left,
color: AppColors.babyPowder,
size: 28,
),
rightChevronIcon: Icon(
Icons.chevron_right,
color: AppColors.babyPowder,
size: 28,
),
),
标题下方的样式天数
在这里,您可以为周末、工作日和假期设置不同的颜色(如果您已设置):
// 日历日样式
daysOfWeekStyle: const DaysOfWeekStyle(
// Weekend days color (Sat,Sun)
weekendStyle: TextStyle(color: AppColors.ultraRed),
),
在上面的代码中,我为我在实现TableCalendar
小部件时最初设置的周末添加了颜色。
日期样式
您可以在此处为特定的周末日期或假期日期添加颜色。此外,可以自定义当前日期和所选日期的突出显示颜色。
// 日历日期样式
calendarStyle: const CalendarStyle(
// Weekend dates color (Sat & Sun Column)
weekendTextStyle: TextStyle(color: AppColors.ultraRed),
// highlighted color for today
todayDecoration: BoxDecoration(
color: AppColors.eggPlant,
shape: BoxShape.circle,
),
// highlighted color for selected day
selectedDecoration: BoxDecoration(
color: AppColors.blackCoffee,
shape: BoxShape.circle,
),
),
接下来的代码块是从所提供的官方文件TableCalender
。这是实现所选日期的默认方式。此代码根据上述自定义颜色突出显示当前日期和选定日期。没有更好的方法可以做到这一点,建议TableCalendar
:
selectedDayPredicate: (currentSelectedDate) {
// as per the documentation 'selectedDayPredicate' needs to determine current selected day.
return (isSameDay(
_selectedCalendarDate!, currentSelectedDate));
},
onDaySelected: (selectedDay, focusedDay) {
// as per the documentation
if (!isSameDay(_selectedCalendarDate, selectedDay)) {
setState(() {
_selectedCalendarDate = selectedDay;
_focusedCalendarDate = focusedDay;
});
}
},
第 3 步:将事件添加到 TableCalendar
所以我们已经完成了初始化TableCalendar
并将其风格化以匹配我们的 UI。剩下的唯一事情就是将事件添加到我们的日历中,这是一项重要功能。没有它,我们的日历只是一份硬拷贝,我们保存在家里或冰箱上。
然而,我们中的许多人倾向于在日历上贴上便利贴来指示整个月、一周甚至一天的关键事件。在我们的手机上,我们可以将提醒或事件添加到我们的默认日历应用程序中。
我创建了一个模型类,命名MyEvents
并初始化了两个 String 变量eventTitle
和eventDescp
:
class MyEvents {
final String eventTitle;
final String eventDescp;
MyEvents({required this.eventTitle, required this.eventDescp});
@override
String toString() => eventTitle;
}
在我们的 CustomCalendarTable文件中,我添加了两个
TextEditingControllers、a和一个map,我们将在其中保存我们的事件列表并将其应用于TableCalandar 中的属性:
final titleController = TextEditingController();
final descpController = TextEditingController();
late Map<DateTime, List<MyEvents>> mySelectedEvents;
@override
void initState() {
selectedCalendarDate = _focusedCalendarDate;
mySelectedEvents = {};
super.initState();
}
@override
void dispose() {
titleController.dispose();
descpController.dispose();
super.dispose();
}
List<MyEvents> _listOfDayEvents(DateTime dateTime) {
return mySelectedEvents[dateTime] ?? [];
}
接下来,我向我们添加了一个 fab 按钮,Scaffold
单击 fab 按钮时,将出现一个AlertDialog
,用户将在其中输入事件标题和事件描述。
单击 Add
按钮后出现AlertDialog
,将在日历下添加一个事件,并在添加事件的日期看到一个小彩色圆点。
我还添加了一个SnackBar
以防用户没有在标题文本字段或描述文本字段中输入任何内容。SnackBar
将弹出一条请输入标题和描述的消息/
如果用户输入了标题和描述,则在setState
方法中检查所选事件的列表是否不为空,然后我们将标题和描述添加到MyEvents
模型类并创建MyEvents
.
添加事件后,我们将清除Controller
s 并关闭AlertDialog
:
_showAddEventDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('New Event'),
content: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
buildTextField(
controller: titleController, hint: 'Enter Title'),
const SizedBox(
height: 20.0,
),
buildTextField(
controller: descpController, hint: 'Enter Description'),
], ),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),),
TextButton(
onPressed: () {
if (titleController.text.isEmpty &&
descpController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter title & description'),
duration: Duration(seconds: 3),
), );
//Navigator.pop(context);
return;
} else {
setState(() {
if (mySelectedEvents[selectedCalendarDate] != null) {
mySelectedEvents[selectedCalendarDate]?.add(MyEvents(
eventTitle: titleController.text,
eventDescp: descpController.text));
} else {
mySelectedEvents[selectedCalendarDate!] = [
MyEvents(
eventTitle: titleController.text,
eventDescp: descpController.text)
]; } });
titleController.clear();
descpController.clear();
Navigator.pop(context);
return;
}
},
child: const Text('Add'),
),
],
));}
我已经构建了一个自定义文本字段,该字段已在以下内容中初始化AlertDialog
:
Widget buildTextField(
{String? hint, required TextEditingController controller}) {
return TextField(
controller: controller,
textCapitalization: TextCapitalization.words,
decoration: InputDecoration(
labelText: hint ?? '',
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.eggPlant, width: 1.5),
borderRadius: BorderRadius.circular(
10.0,
),
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.eggPlant, width: 1.5),
borderRadius: BorderRadius.circular(
10.0,
),
),
),
);
}
当我添加小部件eventLoader
下的属性并向其TableCalendar
添加_listofDayEvents
方法时,一切都汇集在一起
// 需要添加这个属性来显示事件
eventLoader: _listOfDayEvents,
就是这样,我们已经成功地实现了将事件添加到日历日期并将其显示在我们的应用程序中的日历下的方法。
正如我在本文前面提到的,有一些优秀的日历库可用,例如 flutter_calendar_carousel 和 syncfusion_flutter_calendar。
所有的基本实现保持不变。甚至属性和自定义也与我TableCalendar
在本文中提到的非常相似。尽管属性的名称不同,但功能保持不变。
我试图包含尽可能多的细节,以帮助希望在其应用程序中集成日历的任何人,但正如我经常说的,发现则去实验,这一直是我的座右铭。因此,请使用代码,如果您需要更多关于这方面的信息,可以关注我。非常感谢!
最后附上完整代码
class CustomTableCalendar extends StatefulWidget {
const CustomTableCalendar({Key? key}) : super(key: key);
@override
_CustomTableCalendarState createState() => _CustomTableCalendarState();
}
class _CustomTableCalendarState extends State<CustomTableCalendar> {
final todaysDate = DateTime.now();
var _focusedCalendarDate = DateTime.now();
final _initialCalendarDate = DateTime(2000);
final _lastCalendarDate = DateTime(2050);
DateTime? selectedCalendarDate;
final titleController = TextEditingController();
final descpController = TextEditingController();
late Map<DateTime, List<MyEvents>> mySelectedEvents;
@override
void initState() {
selectedCalendarDate = _focusedCalendarDate;
mySelectedEvents = {};
super.initState();
}
@override
void dispose() {
titleController.dispose();
descpController.dispose();
super.dispose();
}
List<MyEvents> _listOfDayEvents(DateTime dateTime) {
return mySelectedEvents[dateTime] ?? [];
}
_showAddEventDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('New Event'),
content: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
buildTextField(
controller: titleController, hint: 'Enter Title'),
const SizedBox(
height: 20.0,
),
buildTextField(
controller: descpController, hint: 'Enter Description'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
if (titleController.text.isEmpty &&
descpController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter title & description'),
duration: Duration(seconds: 3),
),
);
//Navigator.pop(context);
return;
} else {
setState(() {
if (mySelectedEvents[selectedCalendarDate] != null) {
mySelectedEvents[selectedCalendarDate]?.add(MyEvents(
eventTitle: titleController.text,
eventDescp: descpController.text));
} else {
mySelectedEvents[selectedCalendarDate!] = [
MyEvents(
eventTitle: titleController.text,
eventDescp: descpController.text)
];
}
});
titleController.clear();
descpController.clear();
Navigator.pop(context);
return;
}
},
child: const Text('Add'),
),
],
));
}
Widget buildTextField(
{String? hint, required TextEditingController controller}) {
return TextField(
controller: controller,
textCapitalization: TextCapitalization.words,
decoration: InputDecoration(
labelText: hint ?? '',
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.eggPlant, width: 1.5),
borderRadius: BorderRadius.circular(
10.0,
),
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.eggPlant, width: 1.5),
borderRadius: BorderRadius.circular(
10.0,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Custom Calendar'),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => _showAddEventDialog(),
label: const Text('Add Event'),
),
body: SingleChildScrollView(
child: Column(
children: [
Card(
margin: const EdgeInsets.all(8.0),
elevation: 5.0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
side: BorderSide(color: AppColors.blackCoffee, width: 2.0),
),
child: TableCalendar(
focusedDay: _focusedCalendarDate,
// today's date
firstDay: _initialCalendarDate,
// earliest possible date
lastDay: _lastCalendarDate,
// latest allowed date
calendarFormat: CalendarFormat.month,
// default view when displayed
// default is Saturday & Sunday but can be set to any day.
// instead of day number can be mentioned as well.
weekendDays: const [DateTime.sunday, 6],
// default is Sunday but can be changed according to locale
startingDayOfWeek: StartingDayOfWeek.monday,
// height between the day row and 1st date row, default is 16.0
daysOfWeekHeight: 40.0,
// height between the date rows, default is 52.0
rowHeight: 60.0,
// this property needs to be added if we want to show events
eventLoader: _listOfDayEvents,
// Calendar Header Styling
headerStyle: const HeaderStyle(
titleTextStyle:
TextStyle(color: AppColors.babyPowder, fontSize: 20.0),
decoration: BoxDecoration(
color: AppColors.eggPlant,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10))),
formatButtonTextStyle:
TextStyle(color: AppColors.ultraRed, fontSize: 16.0),
formatButtonDecoration: BoxDecoration(
color: AppColors.babyPowder,
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
leftChevronIcon: Icon(
Icons.chevron_left,
color: AppColors.babyPowder,
size: 28,
),
rightChevronIcon: Icon(
Icons.chevron_right,
color: AppColors.babyPowder,
size: 28,
),
),
// Calendar Days Styling
daysOfWeekStyle: const DaysOfWeekStyle(
// Weekend days color (Sat,Sun)
weekendStyle: TextStyle(color: AppColors.ultraRed),
),
// Calendar Dates styling
calendarStyle: const CalendarStyle(
// Weekend dates color (Sat & Sun Column)
weekendTextStyle: TextStyle(color: AppColors.ultraRed),
// highlighted color for today
todayDecoration: BoxDecoration(
color: AppColors.eggPlant,
shape: BoxShape.circle,
),
// highlighted color for selected day
selectedDecoration: BoxDecoration(
color: AppColors.blackCoffee,
shape: BoxShape.circle,
),
markerDecoration: BoxDecoration(
color: AppColors.ultraRed, shape: BoxShape.circle),
),
selectedDayPredicate: (currentSelectedDate) {
// as per the documentation 'selectedDayPredicate' needs to determine
// current selected day
return (isSameDay(
selectedCalendarDate!, currentSelectedDate));
},
onDaySelected: (selectedDay, focusedDay) {
// as per the documentation
if (!isSameDay(selectedCalendarDate, selectedDay)) {
setState(() {
selectedCalendarDate = selectedDay;
_focusedCalendarDate = focusedDay;
});
}
},
),
),
..._listOfDayEvents(selectedCalendarDate!).map(
(myEvents) => ListTile(
leading: const Icon(
Icons.done,
color: AppColors.eggPlant,
),
title: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Event Title: ${myEvents.eventTitle}'),
),
subtitle: Text('Description: ${myEvents.eventDescp}'),
),
),
],
),
),
);
}
}
class MyEvents {
final String eventTitle;
final String eventDescp;
MyEvents({required this.eventTitle, required this.eventDescp});
@override
String toString() => eventTitle;
}
版权声明: 本文为 InfoQ 作者【坚果前端】的原创文章。
原文链接:【http://xie.infoq.cn/article/1782c5c7d21d9d81cf6efe1b9】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
坚果前端
此间若无火炬,我便是唯一的光 2020.10.25 加入
公众号:“坚果前端”,51CTO博客首席体验官,专注于大前端技术的分享,包括Flutter,小程序,安卓,VUE,JavaScript。
评论