Flutter2でカレンダーを自作する


## 背景
- カレンダーパッケージを使用せずに、カレンダーを作りたい
- パッケージのものを使うと、融通が効かない
- 始まりの曜日を変更したい

## 前提
- Flutter 2.2.0 使用

## 使っているパッケージ
- バージョンは状況に合わせて変更してください
- flutter_hooks: ^0.17.0
- hooks_riverpod: ^0.14.0+4
- intl: ^0.17.0


## こういうものができます
- 横スクロールで月の変更
    - PageViewを使用しています
- ドロップダウンメニューで曜日始まり変更
    - DropdownButtonを使用しています


![calendar.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267662/9f46537e-1e1c-4a27-7e50-5c1983aa5c8f.gif)


- スタイルなどは適宜変更してください

## カレンダーで必要なリストの作り方
- 一つの月のカレンダーを作成するためには、それぞれのリストを作成し最後にガッちゃんこする感じになります
    - それぞれのリストでView側で使用したいオブジェクトに変換しています。
        - 前月
        - 今月
        - 来月

- 前月

```

  // 前月の日を埋めるリストを作成する
  List<CalendarDate> prevPaddingDays(int offset) {
    final year = targetMonth.year;
    final month = targetMonth.month;
    final list = <CalendarDate>[];
    final firstDay = DateTime(year, month).weekday;
    final paddingDayCount = (firstDay + 7 - offset) % 7;
    final prevLastDate = DateTime(year, month, 0).day;
    for (var day = prevLastDate - paddingDayCount + 1; day < prevLastDate + 1; day++) {
      list.add(CalendarDate(
        year: Year(year),
        month: Month.values[DateTime(year, month - 1).month - 1],
        day: Day(day),
        dayOfWeek: DayOfWeek.values[(DateTime(year, month - 1, day).weekday % 7)],
        enabled: false,
      ));
    }
    return list;
  }
```

- 今月

```
  // 今月の日にちリストを作成する
  List<CalendarDate> currentDays() {
    final year = targetMonth.year;
    final month = targetMonth.month;
    final list = <CalendarDate>[];
    final lastDate = DateTime(year, month + 1, 0);
    final currentDayCount = lastDate.day;
    for (var day = 1; day < currentDayCount + 1; day++) {
      list.add(CalendarDate(
        year: Year(year),
        month: Month.values[month - 1],
        day: Day(day),
        dayOfWeek: DayOfWeek.values[(DateTime(year, month, day).weekday % 7)],
        enabled: true,
      ));
    }
    return list;
  }
```

- 来月

```
  // 来月の日を埋めるリストを作成する
  List<CalendarDate> nextPaddingDays(prevList, currentList) {
    final year = targetMonth.year;
    final month = targetMonth.month;
    final list = <CalendarDate>[];
    final paddingDayCount = (42 - (prevList.length + currentList.length)) % 7;
    for (var day = 1; day < paddingDayCount + 1; day++) {
      list.add(CalendarDate(
        year: Year(year),
        month: Month.values[DateTime(year, month + 1).month - 1],
        day: Day(day),
        dayOfWeek: DayOfWeek.values[(DateTime(year, month + 1, day).weekday % 7)],
        enabled: false,
      ));
    }
    return list;
  }
```


- がっちゃんこして今月のカレンダーリストを作成

```
  /// カレンダーリストを作成する
  /// year: 年
  /// month: 月
  /// offset: 開始の日（日曜日スタートがoffset:0で、月曜日からにする場合はoffset:1にする）
  void createCalendarList({int offset = STARTING_ON_SUNDAY}) {
    final prevList = prevPaddingDays(offset);
    final currentList = currentDays();
    final nextList = nextPaddingDays(prevList, currentList);
    flatCalendarDate = [
      ...prevList,
      ...currentList,
      ...nextList,
    ];

    calendarMonthDate = to2Dim(flatCalendarDate, NUMBER_OF_ONE_WEEK);
    notifyListeners();
  }


  /// ２次元リストを作成する
  List<CalendarWeek> to2Dim(list, numOfElems) {
    if (list.isEmpty) {
      return [];
    }
    return [CalendarWeek(days: list.take(numOfElems).toList()), ...to2Dim(list.skip(numOfElems), numOfElems)];
  }
```


## 終わりに
- 今回は、日付をタップしても何も起こりません
    - カレンダーリスト作成時にオブジェクトに、
        - 今日かどうかのフラグだったり、何かデータを追加したりするなどすると、日付ごとにイベントを行えると思います。
- こちらに今回実装したカレンダーのリポジトリを置いておきます
    - https://github.com/tamamu79/self-calendar


## 参考
- すごく参考にさせていただきました　ありがとうございます。
    - [Vue.jsでカレンダーを自作する](https://qiita.com/wintyo/items/36bdc7aa2952c65a1d60)