본문 바로가기
플러터

플러터 달력 TableCalendar 패키지

by 왕초보 독학 코딩 2024. 3. 17.

앱에서 사용할 달력 위젯을 검색해보니 TableCalendar가 가장 적합해 보였다. TableCalendar에 대해서 알아보자.

 

설치

TableCalendar를 사용하기 위해서는 pubspec.yaml의 dependencies에 다음과 같이 추가하자. (현재 3.1.0이 최신버전이다.)

dependencies:
  table_calendar: ^3.1.0

 

 

기본 사용법

TableCalendar를 사용하려면 firstDay, lastDay, focusedDay를 제공해야 한다.

- firstDay : 달력의 첫 번째 사용 가능한 날짜, 이 날짜의 이전 날짜에는 접근할 수 없다.

- lastDay : 달력의 마지막 사용 가능한 날짜, 이 날짜의 이후 날짜에는 접근할 수 없다.

- focusedDay : 포커스할 날짜, 보통 오늘이 포커스할 날짜로 사용된다.

TableCalendar(
  firstDay: DateTime.utc(2010, 10, 16),
  lastDay: DateTime.utc(2030, 3, 14),
  focusedDay: DateTime.now(),
);

 

다음과 같이 위젯을 만들고 실행시키면 오늘 날짜가 선택된 달력이 보이는 것을 확인할 수 있다.

class TableCalendarExample extends StatefulWidget {
  @override
  _TableCalendarExampleState createState() => _TableCalendarExampleState();
}

class _TableCalendarExampleState extends State<TableCalendarExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TableCalendar Example'),
      ),
      body: Center(
        child: TableCalendar(
          firstDay: DateTime.utc(2010, 10, 16),
          lastDay: DateTime.utc(2030, 3, 14),
          focusedDay: DateTime.now(),
        ),
      ),
    );
  }
}

 

TableCalendar 위젯만 추가하면 수평 스와이프로 월(month)를 바꿀 수 있고, 다른 기능은 작동하지 않는다. TableCalendar 위젯의 속성에 몇 가지 콜백을 지정하면 쉽게 기능을 추가할 수 있다.

 

날짜 선택하기

사용자가 탭한 날짜를 선택되게 하려면 selectedDayPredicate와 onDayselected 속성을 추가해야 한다. 

 

사용자가 날짜를 탭하면, 먼저 onDaySelected() 함수가 호출된다. 그리고 이전에 선택되었던 날짜와 현재 포커스된 날짜의 정보가 전달된다. 그리고 selectedDayPredicate() 함수가 호출되어, 현재 달력의 모든 날짜만큼 호출되며, 호출될 때 날짜를 전달한다. selectedDaypredicate()가 true를 리턴하면 이 날짜는 선택되게 되고, false를 리턴하면 선택되지 않게 된다.

 

다음 코드를 TableCalendar 위제에 추가하면 사용자의 탭에 응답하여 탭된 날짜를 선택한 것으로 표시할 수 있다. 먼저 onDaySelected() 함수가 호출되기 때문에 먼저 이전에 선택되었던 날짜를 _selectedDay 프로퍼티에 저장해둔다. 그리고 호출될 selectedDayPredicated에서 _selectedDay 프로퍼티와 달력의 날짜를 isSameDay()로 비교하여 같은 날짜인 경우 true를 리턴하게 해서, 선택된 날짜만 선택되게 한다.

class _TableCalendarExampleState extends State<TableCalendarExample> {
  DateTime? _selectedDay;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TableCalendar Example'),
      ),
      body: Center(
        child: TableCalendar(
          firstDay: DateTime.utc(2010, 10, 16),
          lastDay: DateTime.utc(2030, 3, 14),
          focusedDay: DateTime.now(),
          selectedDayPredicate: (day) {
            return isSameDay(_selectedDay, day);
          },
          onDaySelected: (selectedDay, focusedDay) {
            setState(() {
              _selectedDay = selectedDay;
            });
          },
        ),
      ),
    );
  }
}

 

 

달력 형식을 동적으로 업데이트하기

위젯에 보이는 달력 형식을 동적으로 업데이트하려면 다음 코드를 추가하자.

 

class _TableCalendarExampleState extends State<TableCalendarExample> {
  DateTime? _selectedDay;
  CalendarFormat _calendarFormat = CalendarFormat.month;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TableCalendar Example'),
      ),
      body: Center(
        child: TableCalendar(
          firstDay: DateTime.utc(2010, 10, 16),
          lastDay: DateTime.utc(2030, 3, 14),
          focusedDay: DateTime.now(),
          selectedDayPredicate: (day) {
            return isSameDay(_selectedDay, day);
          },
          onDaySelected: (selectedDay, focusedDay) {
            setState(() {
              _selectedDay = selectedDay;
            });
          },
          calendarFormat: _calendarFormat,
          onFormatChanged: (format) {
            setState(() {
              _calendarFormat = format;
            });
          },
        ),
      ),
    );
  }
}

 

위의 코드를 추가하면 상단에 달력 형식 변경 버튼이 추가되고, 버튼을 누를 때마다 month > 2week > week 순으로 달력 형식이 변경된다.

 

포커스 날짜 업데이트하기

focusedDay를 정적 값으로 설정하면 TableCalendar 위젯이 다시 빌드될 때마다 해당 특정 fcousedDay를 사용한다. 예를 들어 fcousedDay를 DateTime.now()로 설정하고, 다음 달로 이동한 다음 hot reload를 하면 TableCalendar는 초기 상태로 재설정된다. 이를 방지하려면 다음과 같이 onPageChanged() 속성을 추가하면 된다.

class _TableCalendarExampleState extends State<TableCalendarExample> {
  DateTime _focusedDay = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TableCalendar Example'),
      ),
      body: Center(
        child: TableCalendar(
          firstDay: DateTime.utc(2010, 10, 16),
          lastDay: DateTime.utc(2030, 3, 14),
          focusedDay: _focusedDay,
          onPageChanged: (focusedDay) {
            _focusedDay = focusedDay;
          },
        ),
      ),
    );
  }
}

 

 

이벤트

TableCalendar 위젯에 사용자 지정 이벤트를 제공할 수 있다. 이를 위해 eventLoader 속성을 사용한다. eventLoader 속성은 DateTime 객체를 제공하며, 해당 객체에 이벤트 목록을 할당해야 한다.

eventLoader: (day) {
  return _getEventsForDay(day);
},

 

_getEventsForDay()는 어떤 구현 방식이든 사용할 수 있다. 예를 들어 Map<DateTiem, List<T>>을 사용할 수 있다.

List<Event> _getEventsForDay(DateTime day) {
  return events[day] ?? [];
}

 

Map을 사용하기로 결정한 경우, LinkedHashMap으로 만드는 것이 좋다. 이렇게 하면 두 DateTime 객체간의 동일성 비교를 오버라이드할 수 있으며, 이를 통해 날짜 부분만을 비교할 수 있다.

final events = LinkedHashMap(
  equals: isSameDay,
  hashCode: getHashCode,
)..addAll(eventSource);

 

 

주기적인 이벤트

eventLoader를 사용하면 주기적인 이벤트를 쉽게 추가할 수 있다. 예를 들어, 매주 월요일마다 반복되는 이벤트를 추가하는 예제는 다음과 같다.

eventLoader: (day) {
  if (day.weekday == DateTime.monday) {
    return [Event('Cyclic event')];
  }

  return [];
},

 

 

 

CalendarBuilders를 사용하여 UI 커스텀하기

CalendarBuilders를 이용해서 UI를 선택적으로 재정의하여 매우 구체적인 디자인을 구현할 수 있다.

 

기본 스타일을 사용하려면 모든 빌더에서 null을 반환할 수 있다. 예를 들어, 다음 코드 조각은 일요일의 요일 레이블만 재정의하고 다른 요일 레이블은 변경하지 않는다.

calendarBuilders: CalendarBuilders(
  dowBuilder: (context, day) {
    if (day.weekday == DateTime.sunday) {
      final text = DateFormat.E().format(day);

      return Center(
        child: Text(
          text,
          style: TextStyle(color: Colors.red),
        ),
      );
    }
  },
),

 

 

로컬화

원하는 언어로 캘린더를 표시하려면 locale 속성을 사용하면 된다. 지정하지 않으면 기본 로컬이 사용된다.

 

로컬을 사용하기 전에 날짜 형식을 초기화해야 한다. 먼저 pubspec.yaml파일에 intl 패키지를 추가한다. 그런 다음 main() 함수를 수정한다.

import 'package:intl/date_symbol_data_local.dart';

void main() {
  initializeDateFormatting().then((_) => runApp(MyApp()));
}

 

 

 

이후 언어를 지정하려면 해당 언어의 문자열 코드를 locale 속성에 전달하면 된다. 예를 들어 다음은 TableCalendar를 폴란드어로 사용하도록 만든다.

TableCalendar(
  locale: 'pl_PL',
),

 

참고로 FormatButton의 텍스트 언어를 변경하려면 직접 처리해야 한다. availableCalendarFormats 속성을 사용하여 번역된 문자열을 전달하자. formatButtonVisible을 false로 설정하면 버튼을 완전히 숨길 수도 있다.