Flutter로 To-Do 리스트 앱에 캘린더 기능 추가하기 (TableCalendar 활용) (2)

2024. 12. 17. 07:58같이 공부합시다 - Flutter/Flutter로 To-Do 앱 만들기

728x90
반응형

저번 시간에는 캘린더 패키지를 이용해서 띄우기까지 성공했습니다.
자, 이번 시간에는 Flutter를 사용해 To-Do 리스트 앱에 캘린더 기능을 한층 더 살려봅시다.


캘린더 레벨업에 신나는 스테디빌더


 
 


할 일 리스트에 날짜 정보 추가하기

캘린더와 연동하려면 할 일 리스트에도 날짜 정보가 포함되어야 합니다.
 
<할 일 리스트 확장>

final List<Map<String, dynamic>> _todoList = [
  {'task': '할 일 1', 'isCompleted': false, 'date': '2024-12-13'},
  {'task': '할 일 2', 'isCompleted': true, 'date': '2024-12-13'},
];

이런식으로 말이죠.
 
 


날짜별 이벤트 저장

날짜별로도 할 일을 빠르게 조회하기 위해
Map<DateTime, List<String>> 형태로 저장합니다.
 
Map 은 키-값 쌍으로 데이터를 저장하는 컬렉션입니다.
여기에서의 키는 날짜(DateTime) 이며,
날짜에 따른 할 일 목록(List<String>) 이 됩니다.
 
할 일 목록은 하나가 아니기 때문에,
List<String> 으로 여러 정보를 담습니다.
 
<날짜별 이벤트 저장 변수를 만들었다.>

final Map<DateTime, List<String>> _calendarEvents = {};

 
<날짜별 이벤트 저장은 여기에>


 
추가로 캘린더에서 쓰일 _focusedDay 와 _selectedDay 도 같이 만들어 둡니다.
_focusedDay 는 오늘 날짜이고, _selectedDay 는 사용자가 날짜를 선택했을 때 선택한 날짜를 저장하도록 할 겁니다.
 
 
 


호출부와 처리 메서드 연동

캘린더 UI 호출
앱에서 캘린더를 표시할 위치를 정하고, UI를 호출하는 메서드를 작성합니다.
앱 상단의 캘린더 아이콘을 누르면 캘린더를 호출하도록 설정합니다.

void _showCalendar() {
  showModalBottomSheet(
    context: context,
    isScrollControlled: true,
    builder: (context) {
      return Column(
        children: [
          TableCalendar(
            focusedDay: _focusedDay,
            firstDay: DateTime(2000),
            lastDay: DateTime(2100),
            selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
            onDaySelected: (selectedDay, focusedDay) {
              setState(() {
                _selectedDay = selectedDay;
                _focusedDay = focusedDay;
              });
              Navigator.of(context).pop(); // 캘린더 닫기
            },
          ),
          const SizedBox(height: 16),
          Expanded(
            child: ListView(
              children: _getEventsForDay(_selectedDay ?? _focusedDay)
                  .map((event) => ListTile(
                        title: Text(event),
                      ))
                  .toList(),
            ),
          ),
        ],
      );
    },
  );
}

이 코드는 Flutter에서 TableCalendar 위젯을 사용하여 캘린더를 표시하고, 선택된 날짜와 관련된 이벤트 목록을 함께 보여주는 기능을 구현한 것입니다. 캘린더는 showModalBottomSheet를 통해 하단 모달로 표시됩니다.

 

코드 상세 분석

🔸showModalBottomSheet

  • Flutter에서 하단 모달 화면을 표시하는 위젯입니다.
  • 사용자 정의 UI를 추가할 수 있고, 하단에서 화면 일부를 덮어쓰는 스타일로 표시됩니다.

매개변수

  • context: 현재 위젯 트리의 빌드 컨텍스트를 전달합니다.
  • isScrollControlled: true:
    • 모달 높이를 사용자가 추가하는 콘텐츠에 맞게 조절하도록 설정합니다.

<showModalBottomSheet 위젯 isScrollControlled 속성에 따른 UI 차이>

  • builder: 모달에 표시할 UI를 빌드하는 함수입니다.

 
 

🔸 Column

모달 내에서 캘린더와 이벤트 목록을 세로로 배치하기 위해 사용됩니다.
Column 구성 요소

  1. TableCalendar:
    • Flutter 패키지에서 제공하는 캘린더 위젯입니다.
    • 달력과 날짜를 선택할 수 있는 기능을 제공합니다.
    • 주요 속성과 동작:
      • focusedDay: 캘린더가 현재 표시할 날짜를 지정합니다.
      • firstDay / lastDay: 캘린더에서 선택 가능한 날짜의 범위를 설정합니다.
      • selectedDayPredicate: 특정 날짜가 선택되었는지 여부를 정의하는 콜백 함수입니다.
      • onDaySelected: 날짜를 선택했을 때 호출되는 콜백 함수입니다.
        • 선택된 날짜(selectedDay)와 포커스된 날짜(focusedDay)를 업데이트합니다.
        • Navigator.of(context).pop()을 호출하여 모달을 닫습니다.
  2. const SizedBox(height: 16):
    • 캘린더와 이벤트 목록 사이에 16픽셀의 간격을 만듭니다.
  3. Expanded:
    • 이벤트 목록을 캘린더 아래에서 남은 공간을 채우며 확장되도록 설정합니다.
  4. ListView:
    • 선택된 날짜의 이벤트를 스크롤 가능한 리스트로 표시합니다.
    • _getEventsForDay() 함수가 반환하는 이벤트 목록을 기반으로 ListTile을 생성합니다.

 
 
 


할 일 추가와 캘린더 데이터 연동

할 일을 추가할 때 날짜 정보를 저장하고, calendarEvents에 추가합니다.

void _addTodoItem(String task) {
  final today = DateTime.now();
  final dateKey = DateTime(today.year, today.month, today.day);

  setState(() {
    _todoList.add({'task': task, 'isCompleted': false, 'date': today.toIso8601String()});
    _calendarEvents[dateKey] = _calendarEvents[dateKey] ?? [];
    _calendarEvents[dateKey]!.add(task);
  });
}

today.toIso8601String() 메서드는 ISO 8601 표준 형식의 문자열로 날짜와 시간을 반환합니다.
이 형식은 국제적으로 널리 사용되며, 데이터 전송 및 저장에 유용합니다.
 

데이터 예시

final today = DateTime.now();
print(today.toIso8601String());

출력 예시

2024-12-16T14:30:15.123

 

형식 설명

  • 2024-12-16: 날짜 (년-월-일)
    • 2024: 연도
    • 12: 월 (12월)
    • 16: 일 (16일)
  • T: 날짜와 시간 구분자
  • 14:30:15.123: 시간 (시:분:초.밀리초)
    • 14: 24시간 형식의 시 (오후 2시)
    • 30: 분
    • 15: 초
    • .123: 밀리초

 
 
 


날짜별 데이터 조회 메서드

선택된 날짜나 현재 포커스된 날짜에 해당하는 이벤트 목록을 반환하는 역할을 합니다.
이 함수는 캘린더와 이벤트 목록을 연동하는 데 중요한 역할을 합니다.

List<String> _getEventsForDay(DateTime day) {
  final dateKey = DateTime(day.year, day.month, day.day);
  return _calendarEvents[dateKey] ?? [];
}

 

  1. 특정 날짜에 대한 이벤트 조회:
    • _selectedDay 또는 _focusedDay를 기준으로, 해당 날짜와 관련된 이벤트를 가져옵니다.
  2. 리턴값:
    • 해당 날짜에 관련된 이벤트들을 List<String> 형태로 반환합니다.

 

코드 상세 분석

 

1. 이벤트 데이터 구조

  • _calendarEvents는 날짜별 이벤트를 저장하는 Map<DateTime, List<String>> 형태입니다.
    • Key: DateTime – 날짜
    • Value: List<String> – 해당 날짜의 이벤트 목록
  • 이 구조를 통해 날짜별로 이벤트를 쉽게 조회할 수 있습니다.

예시 데이터

{
  DateTime(2024, 12, 10): ['회의 참석', 'Flutter 강의 듣기'],
  DateTime(2024, 12, 11): ['운동하기', '친구와 저녁 식사']
}

 

2. 날짜에 대한 이벤트 조회

_calendarEvents[dateKey] ?? [];

  • _calendarEvents[dateKey]:
    • dateKey 에 해당하는 날짜의 이벤트를 가져옵니다.
    • 해당 날짜가 없으면 null을 반환합니다.
  • ?? []:
    • null일 경우 빈 리스트([])를 반환합니다.
    • 이를 통해 특정 날짜에 이벤트가 없을 때도 에러 없이 처리됩니다.

 
 
 


_getEventsForDay가 반환된 데이터 활용

이 함수는 반환된 이벤트 데이터를 리스트 형태로 UI에 연결하는 데 사용됩니다.

사용 예시

Expanded(
  child: ListView(
    children: _getEventsForDay(_selectedDay ?? _focusedDay)
        .map((event) => ListTile(
              title: Text(event),
            ))
        .toList(),
  ),
),

코드 설명

  1. _getEventsForDay(_selectedDay ?? _focusedDay):
    • 선택된 날짜(_selectedDay)가 있으면 해당 날짜의 이벤트를 가져옵니다.
    • 없으면 현재 포커스된 날짜(_focusedDay)를 기준으로 이벤트를 가져옵니다.
    • 예: ['회의 참석', 'Flutter 강의 듣기']
  2. .map((event) => ListTile(...)):
    • 반환된 이벤트 리스트를 순회하면서, 각 이벤트를 ListTile로 변환합니다.
    • 예: '회의 참석' → ListTile(title: Text('회의 참석'))
  3. .toList():
    • Iterable 형태의 데이터를 List로 변환하여 UI에서 사용할 수 있도록 합니다.

 
 
 


캘린더 디자인 개선

  • 날짜 아래에 간단한 이벤트 마커를 표시하도록 TableCalendar의 CalendarBuilders를 활용합니다.
calendarBuilders: CalendarBuilders(
  markerBuilder: (context, day, events) {
    final eventList = _getEventsForDay(day);
    if (eventList.isNotEmpty) {
      return Positioned(
        bottom: 1,
        child: Container(
          width: 8.0,
          height: 8.0,
          decoration: BoxDecoration(
            color: Colors.blue,
            shape: BoxShape.circle,
          ),
        ),
      );
    }
    return null;
  },
),

 

 


날짜별 상세 보기

캘린더에서 특정 날짜를 클릭했을 때 선택된 날짜의 할 일을 메인 화면에 표시하도록 설정합니다.

void _showTasksForSelectedDay() {
  final selectedDateTasks = _getEventsForDay(_selectedDay ?? DateTime.now());
  showModalBottomSheet(
    context: context,
    builder: (context) {
      return ListView(
        children: selectedDateTasks
            .map((task) => ListTile(
                  title: Text(task),
                ))
            .toList(),
      );
    },
  );
}

 

 


전체 흐름 요약

  1. UI 설계
    • 캘린더 UI: TableCalendar로 구현.
    • 선택된 날짜 아래에 할 일 표시.
  2. 데이터 설계
    • 날짜별로 할 일을 저장하는 데이터 구조 확장.
    • Map<DateTime, List<String>>로 데이터 저장.
  3. 메서드 연동
    • 캘린더 호출, 할 일 추가 시 날짜 정보 연동.
    • 선택된 날짜의 할 일을 표시.
  4. 점진적 개선
    • 디자인 개선: 날짜 아래 마커 표시.
    • 선택된 날짜의 상세 보기 기능 추가.

 
 
원했던 캘린더 기능이 어느정도 완성되었네요.
오늘도 고생하셨습니다 !
 
끝 !
 
깃허브 : todo_app/lib/main.dart at main · SteadyBuilder/todo_app

728x90
반응형