Last Updated on 2021-10-13 by Clay
日曆元件是在許多 APP 中都會使用到的元件,比方說記帳、記事相關方面的 APP。而在使用 Flutter 進行開發時,我們可以不用重頭開始造輪子,而是呼叫前輩大神們所開發的日曆套件。
今天我要介紹的是,是在 Flutter pub 上也幾乎是最受歡迎的套件:table_calendar。
準備工作
首先,自然得在 pubspec.yaml 文件底下寫入想要使用的套件。
dependencies:
flutter:
sdk: flutter
table_calendar: ^2.3.3
並使用以下指令取得套件。
flutter pub get
範例程式碼介紹
以下我一步步介紹如何使用程式碼呼叫此套件建立日曆元件。這個範例程式碼稍微有點長,若是想要直接試跑,可以直接看下一小節的完整程式碼。
匯入套件
首先,自然得匯入所需要的套件。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:table_calendar/table_calendar.dart';
(Optional)設定假日
// Holiday will display red word
final Map<DateTime, List> _holidays = {
DateTime(2021, 3, 1): ["228"],
};
在 table_calendar 中,我們可以設定所需要的假日。在這裡可以設定很多項。接口則會放在後續建立日曆物件時。
撰寫 HomePage 頁面
// Main
void main() {
initializeDateFormatting().then((_) => runApp(MyApp()));
}
// MyApp
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Hide the top status bar
SystemChrome.setEnabledSystemUIOverlays([]);
return MaterialApp(
home: HomePage(title: "Table Calender"),
debugShowCheckedModeBanner: false,
);
}
}
// HomePage
class HomePage extends StatefulWidget {
final String title;
HomePage({Key key, this.title}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
這部分是 Flutter APP 的起手式: main()
進入點、HomePage
進入時的介面等等。
接著下一步才是撰寫 _HomePageState
中所有互動的功能。
_HomePageState 中初始狀態
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
Map<DateTime, List> _events;
List _selectedEvents;
AnimationController _animationController;
CalendarController _calendarController;
@override
void initState() {
super.initState();
// Today
final _selectedDay = DateTime.now();
// Events
_events = {
_selectedDay.add(Duration(days: 1)): [
'Sleep all day',
],
_selectedDay.add(Duration(days: 2)): [
'Play PS4 Game',
'Exercising',
'Coding',
'Sleeping',
'Watch a movie',
'Take a walk',
'Surf on the internet'
],
};
_selectedEvents = _events[_selectedDay] ?? [];
_calendarController = CalendarController();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_animationController.forward();
}
_selectedEvents
為選擇事件的列表_animationController
則是控制動畫_calendarController
顧名思義便是控制日曆元件的物件
下面 _events
則是我隨意建立的活動事件。我想像我是製作一個像行事曆一樣的 APP,一天裡面則是紀錄著我有可能要從事的活動。
重寫一些函式(仍在 _HomePageState 底下)
@override
void dispose() {
_animationController.dispose();
_calendarController.dispose();
super.dispose();
}
void _onDaySelected(DateTime day, List events, List holidays) {
print("CALLBACK: _onDaySelected");
setState(() {
_selectedEvents = events;
});
}
void _onVisibleDaysChanged(DateTime first, DateTime last, CalendarFormat format) {
print("CALLBACK: _onVisibleDaysChanged");
}
void _onCalendarCreated(DateTime first, DateTime last, CalendarFormat format) {
print("CALLBACK: _onCalendarCreated");
}
這裡我們重寫了一些函式,主要是之後讓我們在跟日曆元件互動時能看到 LOG 的訊息。這部分是最初 table_calendar 的說明文件上就有寫著的。這邊我也就直接保留,沒有修改。
介面排列的形式(仍在 _HomePageState 底下)
// Basic interface
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
_buildTableCalendar(),
Expanded(child: _buildEventList()),
],
),
);
}
這裡最重要的便是 Column 元件底下的佈局。既然是包在 Column 元件,那代表我的佈局是從上到下排列。
接著我們再來實作 _buildTableCalendar()
以及 _buildEventList()
這兩個方法。
_buildEventList() (仍在 _HomePageState 底下)
// Event List
Widget _buildEventList() {
return ListView(
children: _selectedEvents.map(
(event) => Container(
decoration: BoxDecoration(
color: Colors.lightBlueAccent[100],
border: Border.all(width: 0.8),
borderRadius: BorderRadius.circular(12.0),
),
margin: const EdgeInsets.symmetric(
horizontal: 2.0,
vertical: 1.0,
),
child: ListTile(
title: Text(event.toString()),
onTap: () => print("${event} tapped!"),
),
)
).toList(),
);
}
這裡看得出來是以 ListView 元件製作,並將我最初設定的 _events
事件帶入。其他的便是一些顏色長寬之類的參數設定。
_buildTableCalendar() (仍在 _HomePageState 底下)
// Simple TableCalendar configuration (using Style)
Widget _buildTableCalendar() {
return TableCalendar(
calendarController: _calendarController,
events: _events,
holidays: _holidays,
startingDayOfWeek: StartingDayOfWeek.sunday,
// Calendar
calendarStyle: CalendarStyle(
selectedColor: Colors.lightBlueAccent[400],
todayColor: Colors.lightBlueAccent[100],
markersColor: Colors.lightBlue[600],
outsideDaysVisible: false,
),
// Header
headerStyle: HeaderStyle(
formatButtonTextStyle:
TextStyle().copyWith(
color: Colors.white,
fontSize: 15.0,
),
formatButtonDecoration: BoxDecoration(
color: Colors.grey[600],
borderRadius: BorderRadius.circular(8.0),
),
),
// Operating
onDaySelected: _onDaySelected,
onVisibleDaysChanged: _onVisibleDaysChanged,
onCalendarCreated: _onCalendarCreated,
);
}
這部分是日曆元件最需要研究的部分。目前我也尚未完全弄清楚每個接口的使用方法。不過就我看到的一些網路上的範例來說,能改的地方還真不少。
主要就是建立 TableCalendar 這個元件。
執行結果請參考下方完整程式碼。即便加上空格也在兩百行以內,其實算是精簡的範例程式碼了。
完整程式碼
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:table_calendar/table_calendar.dart';
// Holiday will display red word
final Map<DateTime, List> _holidays = {
DateTime(2021, 3, 1): ["228"],
};
// Main
void main() {
initializeDateFormatting().then((_) => runApp(MyApp()));
}
// MyApp
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Hide the top status bar
SystemChrome.setEnabledSystemUIOverlays([]);
return MaterialApp(
home: HomePage(title: "Table Calender"),
debugShowCheckedModeBanner: false,
);
}
}
// HomePage
class HomePage extends StatefulWidget {
final String title;
HomePage({Key key, this.title}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
Map<DateTime, List> _events;
List _selectedEvents;
AnimationController _animationController;
CalendarController _calendarController;
@override
void initState() {
super.initState();
// Today
final _selectedDay = DateTime.now();
// Events
_events = {
_selectedDay.add(Duration(days: 1)): [
'Sleep all day',
],
_selectedDay.add(Duration(days: 2)): [
'Play PS4 Game',
'Exercising',
'Coding',
'Sleeping',
'Watch a movie',
'Take a walk',
'Surf on the internet'
],
};
_selectedEvents = _events[_selectedDay] ?? [];
_calendarController = CalendarController();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
_calendarController.dispose();
super.dispose();
}
void _onDaySelected(DateTime day, List events, List holidays) {
print("CALLBACK: _onDaySelected");
setState(() {
_selectedEvents = events;
});
}
void _onVisibleDaysChanged(DateTime first, DateTime last, CalendarFormat format) {
print("CALLBACK: _onVisibleDaysChanged");
}
void _onCalendarCreated(DateTime first, DateTime last, CalendarFormat format) {
print("CALLBACK: _onCalendarCreated");
}
// Basic interface
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
_buildTableCalendar(),
Expanded(child: _buildEventList()),
],
),
);
}
// Simple TableCalendar configuration (using Style)
Widget _buildTableCalendar() {
return TableCalendar(
calendarController: _calendarController,
events: _events,
holidays: _holidays,
startingDayOfWeek: StartingDayOfWeek.sunday,
// Calendar
calendarStyle: CalendarStyle(
selectedColor: Colors.lightBlueAccent[400],
todayColor: Colors.lightBlueAccent[100],
markersColor: Colors.lightBlue[600],
outsideDaysVisible: false,
),
// Header
headerStyle: HeaderStyle(
formatButtonTextStyle:
TextStyle().copyWith(
color: Colors.white,
fontSize: 15.0,
),
formatButtonDecoration: BoxDecoration(
color: Colors.grey[600],
borderRadius: BorderRadius.circular(8.0),
),
),
// Operating
onDaySelected: _onDaySelected,
onVisibleDaysChanged: _onVisibleDaysChanged,
onCalendarCreated: _onCalendarCreated,
);
}
// Event List
Widget _buildEventList() {
return ListView(
children: _selectedEvents.map(
(event) => Container(
decoration: BoxDecoration(
color: Colors.lightBlueAccent[100],
border: Border.all(width: 0.8),
borderRadius: BorderRadius.circular(12.0),
),
margin: const EdgeInsets.symmetric(
horizontal: 2.0,
vertical: 1.0,
),
child: ListTile(
title: Text(event.toString()),
onTap: () => print("${event} tapped!"),
),
)
).toList(),
);
}
}
Output:
References
- https://pub.dev/packages/table_calendar
- https://pub.dev/documentation/table_calendar/latest/table_calendar/TableCalendar-class.html
- https://protocoderspoint.com/flutter-calender-widget-example-table-calender-widget/