#はじめに
Flutterのパッケージにtable_calendarといういい感じでカレンダーを実装できるものがあるのですが、
日本語で紹介された記事があまりなかったので実際に使ってみて実装するまでの記録をまとめたいと思います。
以下のサイトを参考に実装しました。
table_calendar
https://pub.dev/packages/table_calendar
https://github.com/aleksanderwozniak/table_calendar
今回使用するバージョンは以下の通りです。
Flutter 2.0.6
Dart 2.12.3
table_calendar 3.0.0
intl 0.17.0 (言語対応に使用。)
・カレンダーの表示(日本語)
・イベントマーカーの表示
・カレンダーのしたにイベントをリスト表示
の3点を達成目標として進めて行きます。
#インストール
まずはパッケージのインストールをします。
environment:
sdk: ">=2.12.3 <3.0.0" //dartのsdkを手動で2.12.3に変更します。
dependencies:
flutter:
sdk: flutter
table_calendar: ^3.0.0
intl: ^0.17.0
追加できたらpub get
を実行。
#基本設定
まず、TableCalendarではfirstDay
、lastDay
、focusedDay
がrequiresとして設定されてます。
firstDay
とlastDay
はカレンダーを表示する最初と最後の月の指定ができ、
focusedDay
はデフォルトでフォーカスされて日付の部分に色が付きます。
DateTime _focusedDay = DateTime.now();
TableCalendar(
// 以下必ず設定が必要
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
);
#カレンダーフォーマット
ここからtable_calendarをより実践的なものにしていきます。
まずはカレンダーのフォーマット設定です。
赤く囲ったボタンを押すと3パターンのフォーマットに変更するように設定します。
DateTime _focusedDay = DateTime.now();
CalendarFormat _calendarFormat = CalendarFormat.month;
TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
calendarFormat: _calendarFormat, //以下、追記部分。
// フォーマット変更のボタン押下時の処理
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
),
CalendarFormat.month
←デフォルト設定時の表示
ボタンを押すか、カレンダー部分を上下にスワイプさせることでフォーマットを任意で変えることができます。
フォーマットのデフォルトを設定し、ボタン表示をなしにすることも可能です。
headerStyle: HeaderStyle(
formatButtonVisible: false,
),
#日付選択
focusedDay
によって今日の日付に印がついていますが、
今度はタップした日付が選択され、印が付くように設定します。
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay; //追記
CalendarFormat _calendarFormat = CalendarFormat.month;
TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
selectedDayPredicate: (day) { //以下追記部分
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
}
},
),
どの日が現在選択されているかを判断するには、selectedDayPredicate
を使用します。
これがtrue
を返した場合、day
が選択されたとみなされます。
isSameDay
は、比較されたDateTimeオブジェクトの時間部分を無視するために公式より推奨されています。
onDaySelected
と合わせて使用することで、タップした日付に印が付きます。
(21日をタップした時の画面↓)
#イベントの読み込み
TableCalendarの特徴の一つでもあるイベントの読み込みとカレンダーへの反映部分を実装していきます。
ここからコードが少し見にくいかもしれません。。。
class CalendarScreen extends StatefulWidget {
@override
_CalendarScreenState createState() => _CalendarScreenState();
}
class _CalendarScreenState extends State<CalendarScreen> {
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
Map<DateTime, List> _eventsList = {};
int getHashCode(DateTime key) {
return key.day * 1000000 + key.month * 10000 + key.year;
}
@override
void initState() {
super.initState();
_selectedDay = _focusedDay;
//サンプルのイベントリスト
_eventsList = {
DateTime.now().subtract(Duration(days: 2)): ['Event A6', 'Event B6'],
DateTime.now(): ['Event A7', 'Event B7', 'Event C7', 'Event D7'],
DateTime.now().add(Duration(days: 1)): [
'Event A8',
'Event B8',
'Event C8',
'Event D8'
],
DateTime.now().add(Duration(days: 3)):
Set.from(['Event A9', 'Event A9', 'Event B9']).toList(),
DateTime.now().add(Duration(days: 7)): [
'Event A10',
'Event B10',
'Event C10'
],
DateTime.now().add(Duration(days: 11)): ['Event A11', 'Event B11'],
DateTime.now().add(Duration(days: 17)): [
'Event A12',
'Event B12',
'Event C12',
'Event D12'
],
DateTime.now().add(Duration(days: 22)): ['Event A13', 'Event B13'],
DateTime.now().add(Duration(days: 26)): [
'Event A14',
'Event B14',
'Event C14'
],
};
}
@override
Widget build(BuildContext context) {
final _events = LinkedHashMap<DateTime, List>(
equals: isSameDay,
hashCode: getHashCode,
)..addAll(_eventsList);
List getEventForDay(DateTime day) {
return _events[day] ?? [];
}
return Scaffold(
appBar: AppBar(
title: Text('calendar sample'),
),
body: Column(
children: [
TableCalendar(
//locale: 'ja_JP',
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
eventLoader: getEventForDay, //追記
calendarFormat: _calendarFormat,
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
}
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
),
],
),
);
}
}
できるだけ細かく解説していきます。
今回表示させるイベントサンプルは_eventsList
で定義しました。
まずイベントを読み込むためにeventLoader
を使用します。
eventLoader
に関わってくる部分は以下の部分です。
// DateTime型から20210930の8桁のint型へ変換
int getHashCode(DateTime key) {
return key.day * 1000000 + key.month * 10000 + key.year;
}
//...中略
final _events = LinkedHashMap<DateTime, List>(
equals: isSameDay,
hashCode: getHashCode,
)..addAll(_eventsList);
List _getEventForDay(DateTime day) {
return _events[day] ?? [];
}
//...中略
eventLoader: _getEventForDay,
TableCalendarでは、カレンダーに読み込むイベントをMapで定義した場合、LinkedHashMap
を使用することを推奨されています。
純粋に日付だけを比較することができるそうです。
getHashCode
、LinkedHashMap
の部分に関しては公式のサンプルを参考にしています。
これらを設定し終えると、カレンダー部分にマーカーが表示されます。
このマーカーの数は今回で言うと、_eventsList
の各日付のイベントの数に対応しています。
このマーカーはCalendarBuilders
クラスのmarkerBuilder
を使用してカスタマイズすることも可能です。
後ほど例を紹介します。
#イベントのリスト表示
次に実践することとして、タップした日付のイベントの詳細をカレンダーの下にリスト表示したいと思います。
//省略
return Scaffold(
appBar: AppBar(
title: Text('calendar sample'),
),
body: Column(
children: [
TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
eventLoader: _getEventForDay,
calendarFormat: _calendarFormat,
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
_getEventForDay(selectedDay); //追記部分
}
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
),
//以下追記
ListView(
shrinkWrap: true,
children: _getEventForDay(_selectedDay!)
.map((event) => ListTile(
title: Text(event.toString()),
))
.toList(),
)
],
),
);
onDaySelected
内でeventLoader
で定義したものと同じメソッドを定義します。
さらに同じメソッドをListView
を使ってリスト表示させます。
すると、タップした日付のイベントがカレンダーの下にリスト表示されました。
#言語対応
table_calendarでは言語対応もされており、日本語もあります。
設定方法も簡単でした。
はじめにpubspec.yaml
で追加した
intl: ^0.17.0
が別途必要になります。
main()
を以下のように書き換えます。
void main() {
initializeDateFormatting().then((_) =>runApp(MyApp()));
}
locale
で設定できます。
TableCalendar(
locale: 'ja_JP', //追記部分
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
#カスタマイズ例
table_calendarパッケージでは好みに合わせてカレンダーのデザインを細かくカスタマイズすることができます。
今回はその一例を紹介します。
カレンダーのマーカーを数字で表示してみました。
//TableCalendar内
calendarBuilders: CalendarBuilders(
markerBuilder: (context, date, events) {
if (events.isNotEmpty) {
return _buildEventsMarker(date, events);
}
},
Widget _buildEventsMarker(DateTime date, List events) {
return Positioned(
right: 5,
bottom: 5,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red[300],
),
width: 16.0,
height: 16.0,
child: Center(
child: Text(
'${events.length}',
style: TextStyle().copyWith(
color: Colors.white,
fontSize: 12.0,
),
),
),
),
);
}
上記コードでイベントの数を数字で表示することができました。
こちらの写真ではcalendarBuilders
以外に、
daysOfWeekStyle
、calendarStyle
、headerStyle
、を使ってカスタマイズしています。
table_calendar libraryを参考にしました。
https://pub.dev/documentation/table_calendar/latest/table_calendar/table_calendar-library.html#classes
#終わりに
記事の中で間違った情報などあればコメントで指摘していただけますと幸いです。
今回のコードです。
https://github.com/Ryota-Nakamura-317/table_calendar_sample