FlutterのTableCalendarでイベントがある日のデザインを変える

# はじめに
https://pub.dev/packages/table_calendar

TableCalendarには`eventLoader`が存在します。
これはカレンダー上にイベントがある日を表示するために使います。
どうやらデフォルトでは以下のように、カレンダー上に黒丸が表示されるようです。黒丸が3つなら、その日にイベントが3つあるということですね。


![Screenshot 2024-09-09 at 16.39.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/995720f5-5401-9b70-d123-7f35a95612c8.png)


このデザインはどうすれば変えられるのでしょうか？

# まずはイベントを作成し読み込ませる

https://github.com/aleksanderwozniak/table_calendar/blob/master/example/lib/utils.dart

ほとんどサンプルのままですが、こんな感じで作ります。

```dart:lib/models/dayEvent.dart
import 'dart:collection';
import 'package:table_calendar/table_calendar.dart';

class DayEvent {
  final String title;
  const DayEvent(this.title);

  @override
  String toString() => title;
}

final kEvents = LinkedHashMap<DateTime, List<DayEvent>>(
  equals: isSameDay,
  hashCode: getHashCode,
)..addAll(_kEventSource);

final _kEventSource = {
  for (var item in List.generate(50, (index) => index))
    DateTime.utc(kFirstDay.year, kFirstDay.month, item * 5): List.generate(
        item % 4 + 1, (index) => DayEvent('Event $item | ${index + 1}'))
}..addAll({
    kToday: [
      const DayEvent('Today\'s Event 1'),
    ],
  });

int getHashCode(DateTime key) {
  return key.day * 1000000 + key.month * 10000 + key.year;
}

/// Returns a list of [DateTime] objects from [first] to [last], inclusive.
List<DateTime> daysInRange(DateTime first, DateTime last) {
  final dayCount = last.difference(first).inDays + 1;
  return List.generate(
    dayCount,
    (index) => DateTime.utc(first.year, first.month, first.day + index),
  );
}

final kToday = DateTime.now();
final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
```

`kEvents`が作成されたイベントです。
次にこれを`eventLoader`で読み込ませます。

```dart:lib/views/pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:h_calender/models/calendar.dart';
import 'package:h_calender/models/dayEvent.dart';
import 'package:h_calender/theme.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class _TableCalendar extends ConsumerWidget {
  const _TableCalendar();

  List<DayEvent> _getEventsForDay(DateTime day) {
    return kEvents[day] ?? [];
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final selectedDate = ref.watch(selectedDateProvider);
    final focusedDate = ref.watch(focusedDateProvider);
    final colorScheme = Theme.of(context).colorScheme;

    return TableCalendar(
      firstDay: DateTime.utc(2010, 10, 16),
      lastDay: DateTime.utc(2030, 3, 14),
      focusedDay: focusedDate,
      currentDay: DateTime.now(),
      selectedDayPredicate: (day) {
        return isSameDay(selectedDate, day);
      },
      locale: 'ja_JP',
      eventLoader: _getEventsForDay, // 追加
    );
  }
}
```

これでイベントが表示されるようになりました！

![Simulator Screenshot - iPhone SE (3rd generation) - 2024-09-09 at 18.45.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/fc37750c-bcb4-75c3-fbfa-f65565a0ec3f.png)

次はこの表示をカスタマイズします！

# calendarBuildersを追加する

https://github.com/aleksanderwozniak/table_calendar?tab=readme-ov-file#custom-ui-with-calendarbuilders

カレンダーをカスタマイズしたい場合は`CalendarBuilders`を使うようです。
これでイベントがあった日のデザインを変えてみようと思います。

https://happyhaelee.tistory.com/10

```dart
import 'package:flutter/material.dart';
import 'package:h_calender/models/calendar.dart';
import 'package:h_calender/models/dayEvent.dart';
import 'package:h_calender/theme.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class _TableCalendar extends ConsumerWidget {
  const _TableCalendar();

  List<DayEvent> _getEventsForDay(DateTime day) {
    return kEvents[day] ?? [];
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final colorScheme = Theme.of(context).colorScheme;

    return TableCalendar(
      firstDay: DateTime.utc(2010, 10, 16),
      lastDay: DateTime.utc(2030, 3, 14),
      focusedDay: focusedDate,
      currentDay: DateTime.now(),
      selectedDayPredicate: (day) {
        return isSameDay(selectedDate, day);
      },
      locale: 'ja_JP',
      eventLoader: _getEventsForDay,
      headerStyle: const HeaderStyle(
        formatButtonVisible: false,
        titleCentered: true,
      ),
      calendarStyle: CalendarStyle(
        todayDecoration: const BoxDecoration(
          color: AppTheme.darkGray, // 今日の日付の背景色
          shape: BoxShape.circle, // 形を丸く
        ),
        selectedDecoration: BoxDecoration(
            shape: BoxShape.circle, color: colorScheme.secondary // 選択した日付の背景色
            ),
        todayTextStyle: const TextStyle(
          color: Colors.white, // 今日の日付の文字色
        ),
        selectedTextStyle: const TextStyle(
          color: Colors.black, // 選択した日付の文字色
        ),
      ),
      calendarBuilders: CalendarBuilders(
        markerBuilder: (context, date, events) {
          return _CalendarBuildersStack(date: date, events: events);
        },
      ),
    );
  }
}

class _CalendarBuildersStack extends StatelessWidget {
  const _CalendarBuildersStack({
    required this.events,
    required this.date,
  });

  final List events;
  final DateTime date;

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;

    return Stack(
      alignment: Alignment.center,
      children: [
        // 日付の背景色をイベントがある場合に変更
        Container(
          width: 35,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: events.isNotEmpty ? colorScheme.primary : null,
          ),
        ),
        // 日付のテキスト
        Center(
          child: Text(
            date.day.toString(),
            style: const TextStyle(color: Colors.black),
          ),
        ),
      ],
    );
  }
}
```

![Simulator Screenshot - iPhone SE (3rd generation) - 2024-09-09 at 19.07.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/bb694731-aec4-7895-1a22-835cbee60578.png)

これでカスタマイズできました！




# 参考

https://happyhaelee.tistory.com/10