詳解 TableCalendar
前回のTextFieldの引き続き、今回はTableCalendarパッケージを深掘りしていきます。
初学者の方も経験者の方も、TableCalendarの知見を深められると思いますので、気楽に、楽しく読んでいただければ幸いです。また、今回もFlutter for webで実際に触れるようにしました。コードも公開していますので、ご自由にお使いください。
対応プラットフォーム
主要なプラットフォームには全て対応しています。
- Android
- iOS
- Linux
- macOS
- web
- Windows
インストール
以下のコマンドを実行し、table_calendarをインストールします。こちらはflutter pub getも実行してくれます。
flutter pub add table_calendar
もしくは、pubspec.yamlに以下を追加してください。
dependencies:
# 適宜、最新版を指定してください
table_calendar: ^3.0.6
入門 TableCalendar
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarBasics
TableCalendarの使用はシンプルです。
名前付き必須引数である、firstDay、lastDay、focusedDayにDateTimeオブジェクトを指定すれば、カレンダーが完成します。
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: DateTime.now(),
);
firstDay
カレンダーの初日を表します。ユーザーはこの日にち以前の日にちを参照することはできません。
lastDay
カレンダーの最終日を表します。ユーザーはこの日にち以後を参照することができません。
focusedDay
フォーカスを当てる日にちです。上記の例では2022年8月10日を指定しました。
ローカライズ(日本語化)
デフォルトでは英語が使用されていますが、もちろん日本語化も可能です。
intlパッケージ
日本語化するためには、intlパッケージを利用します。
flutter pub add intl
上記コマンドを実行し、intlパッケージを導入しましょう
その後、main関数を以下のように変更します。
import 'package:intl/date_symbol_data_local.dart';
void main() {
initializeDateFormatting().then((_) => runApp(MyApp()));
}
これで、ローカライズの準備は完了です。
日本語化
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarBasics
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: DateTime.now(),
+ locale: 'ja_JP'
);
ロケール(ja_JP)を指定すれば、日本語化は完了です。もちろん、他のロケールを選択すれば、その言語に対応します。
しかしこのままでは、カレンダーをタップしフォーカスを変更することができません。
カレンダーを操作
様々なコールバックを利用し、カレンダーの操作を可能にします。
日付のフォーカス変更
フォーカス対象の日付を格納する、focusedDayおよび、選択した日にちを格納するselectedDayを変数として置きます。focusedDayは本日(DateTime.now())にしましたが、どの日付でも構いません。
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
selectedDayPredicateおよびisSameDay関数
selectedDayPredicateは、どの日にちが選択されているか確認するために利用します。
実際には、table_calendarが提供する isSameDay関数を返す実装にします。
isSameDayは2つの引数(DateTime)を取り、2つの日付が同じか否かを判定します。
onDaySelected
onDaySelected はカレンダーがタップされるたびに呼ばれるコールバック関数です。以下実装では、_selectedDayおよび_focusedDayを最新の値に更新しています。
これで、isSameDayがtrueを返せば、フォーカスが更新されます。
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: _focusedDay,
+ selectedDayPredicate: (day) {
+ return isSameDay(selectedDay, day);
+ },
+ onDaySelected: (selectedDay, focusedDay) {
+ setState(() {
+ _selectedDay = selectedDay;
+ _focusedDay = focusedDay;
+ });
+ }
);
カレンダーの長さを変更
デフォルトでは1ヶ月が表示されますが、2週間、1週間と変更可能です。
calendarFormat に CalendarFormatを渡せば、カレンダーの長さを変更できます。
CalendarFormatはenumで、
- month
- twoWeeks
- week
があります。
+ CalendarFormat _calendarFormat = CalendarFormat.month;
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: _focusedDay,
+ calendarFormat: _calendarFormat
);
onFormatChanged
また、フォーマット変更時に発火するonFormatChangedというコールバックも用意されています。
CalendarFormat _calendarFormat = CalendarFormat.month;
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: _focusedDay,
calendarFormat: _calendarFormat
+ onFormatChanged: (format) {
+ // do something
+ },
);
例えば、フォーマットの変更時にダイアログを出すことも可能ですね。
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarFormat
TableCalendar 応用
イベント
カレンダーにイベントを設定することが可能です。
以下サンプルでは、2022年8月3日および2022年8月5日に2件ずつイベントを登録しています。
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarEvent
イベントの設定
イベントを設定するためには、eventLoaderコールバック を使用します。
eventLoader
イベントはMap形式で保持します。Mapオブジェクトのkeyはイベントの対象日(DateTime, UTC)、valueはイベント内容(List<String>) です。
final sampleMap = {
DateTime.utc(2022, 8, 3): ['firstEvent', 'secodnEvent'],
DateTime.utc(2022, 8, 5): ['thirdEvent', 'fourthEvent']
};
eventLoaderは表示されている日付全てを返しますので、KeyのDateTimeが合致した値が返され、カレンダーに反映されます。
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: _focusedDay,
+ eventLoader: (date) {
+ return sampleMap[date] ?? [];
+ }
);
イベントの表示
イベントをタップし、イベントを表示することが可能です。
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarEventSelection
イベントデータ
表示するイベントは以下のように置きました。8/3および8/5の2件に対して、イベント2件ずつです。
final sampleEvents = {
DateTime.utc(2022, 8, 3): ['firstEvent', 'secondEvent'],
DateTime.utc(2022, 8, 5): ['thirdEvent', 'fourthEvent']
};
表示イベントの格納
表示するイベントを格納するために、以下の変数を設置します。
List<String> _selectedEvents = [];
日付をタップ後、イベントを_selectedEventsに代入します。タップの検知はonDaySelected で行えます。従って、イベントがある日にちがタップされた時に、イベントが_selectedEventsに格納されます。
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedEvents = sampleEvents[selectedDay] ?? [];
});
}
イベントの表示
イベントは_selectedEventsに格納されましたので、あとは表示するだけです。
今回はCard+ListTileを使用しました。もちろんお好きなWidgetをお使いください。
Expanded(
child: ListView.builder(
itemCount: _selectedEvents.length,
itemBuilder: (context, index) {
final event = _selectedEvents[index];
return Card(
child: ListTile(
title: Text(event),
),
);
},
),
),
以上をまとめると、以下のようになります。
定期イベント
例えば毎週月曜日のイベントを設定する場合、以下のように実装できます。
TableCalendar(
firstDay: DateTime.utc(2010, 1, 1),
lastDay: DateTime.utc(2030, 1, 1),
focusedDay: _focusedDay,
+ eventLoader: (date) {
+ if (day.weekday == DateTime.monday) {
+ return [('cyclic event')];
+ }
+ return []'
+ }
);
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarCyclicEvent
期間の選択
例えば13日から25日のように、期間を選択することが可能です。
期間設定、開始日、終了日
まず、期間の選択を有効にする必要があります。今回は1日だけの選択も可能である、toggeOnを選択しました。
RangeSelectionMode _rangeSelectionMode = RangeSelectionMode.toggledOn;
また、開始日・終了日を格納する変数も用意しておきましょう。
RangeSelectionMode _rangeSelectionMode = RangeSelectionMode.toggledOn;
+ DateTime? _rangeStart;
+ DateTime? _rangeEnd;
TableCalendarで、今までの値を追加します。そして、onRangeSelectedを使用し、開始日、終了日を設定します。
TableCalendar(
rangeSelectionMode: _rangeSelectionMode,
rangeStartDay: _rangeStart,
rangeEndDay: _rangeEnd,
onRangeSelected: (start, end, focusedDay) {
setState(() {
_rangeStart = start;
_rangeEnd = end;
});
}
)
これでカレンダーで期間設定が可能になりました。
コードは以下になります。
1日だけ選択
しかしこのままでは、期間設定後、1日だけの選択をすると、両方の状態が残ってしまいます。
(toggleOnに設定している場合、長押しをすると、1日だけ選択が可能です。)
そのため、日付を選択する度に、startとendにnullを代入し、リセットする必要があります。
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
+ _rangeStart = null;
+ _rangeEnd = null;
}
}
問題なく、単一日および期間の設定ができるようになりました。
https://flutter-dictionary-release.web.app/tableCalendarTop/tableCalendarRangeSelection
コードはこちらです。
複数日の選択
カレンダー上で複数日を選択することが可能です。
選択した日にちを格納する変数を用意します。同日を複数選択することはありませんので、要素の重複のない、Setクラスを活用します。
final Set<DateTime> _selectedDays = <DateTime>{};
日付を選択した際に、Setオブジェクトに要素が既に存在するか確認するロジックを追加し、_selectedDaysに値を追加します。
selectedDayPredicate: (day) => _selectedDays.contains(day),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_focusedDay = focusedDay;
if (_selectedDays.contains(selectedDay)) {
_selectedDays.remove(selectedDay);
} else {
_selectedDays.add(selectedDay);
}
});
}
選択した日にちをクリア
選択した日にちをクリアするためには、_selectedDaysの要素をクリアしてください。
setState(() {
_selectedDays.clear();
});
コードはこちらです。
UIのカスタム
calendarBuildersを使用し、カレンダーのUIを柔軟に変更することが可能です。
日曜日(Sun)を灰色から赤色へ変更しました。
*
*
カレンダーの曜日ラベルを変更するには、CalendarBuildersのdowBuilderを使用します。
以下のように、日曜日の場合、'Sun'を返すロジックを書きます。
calendarBuilders: CalendarBuilders(
dowBuilder: (_, day) {
if (day.weekday == DateTime.sunday) {
final text = DateFormat.E().format(day);
return Center(
child: Text(
text,
style: const TextStyle(color: Colors.red),
),
);
}
return null;
),
またnullを返すことにより、デフォルトのスタイルを適用します。
最後に
長文でしたが、最後まで読んで頂きましてありがとうございました。
不明点、間違い等ございましたら、お気軽にコメントで教えていただけるととても嬉しいです!
参考資料
Discussion