Custom Date Picker 만들기(1)

KyleKim96·2023년 5월 26일
0
post-thumbnail

이번에 프로젝트를 하면서 date picker를 사용해야 할 일이 있었는데 캘린더 형식의 date picker를 사용하고 싶어서 라이브러리를 찾아봤는데 딱 마음에 드는게 없었습니다.

그래서 하나 만들어보고 pub dev에 publish까지 해보는 경험을 쌓으면 좋겠다고 생각하여 하나 만들기로 결심했습니다.

이번 포스팅은 간단하게 기능만 구현해보았습니다.

코드는

import 'package:flutter/material.dart';

class DatePickerWidgetTemp extends StatefulWidget {
  const DatePickerWidgetTemp({
    Key? key,
    required this.selectedList,
    required this.selectDate,
  }) : super(key: key);

  final List<DateTime> selectedList;
  final void Function(DateTime)? selectDate;

  @override
  State<DatePickerWidgetTemp> createState() => _DatePickerWidgetState();
}

class _DatePickerWidgetState extends State<DatePickerWidgetTemp> {
  List<DateTime> _selectedDateList = [];

  List<DateTime> _calender = [];

  late DateTime _viewMonth;

  @override
  void initState() {
    super.initState();

    _viewMonth = DateTime.now();

    viewCalender();
    setState(() {});
  }

  List<String> weekNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Tue', 'Fri', 'Sat'];

  void viewCalender() {
    _calender.clear();

    int startWeekDay = DateTime(_viewMonth.year, _viewMonth.month, 1).weekday == 7 ? 0 : DateTime(_viewMonth.year, _viewMonth.month, 1).weekday;

    print(startWeekDay);

    for (int i = 1; i <= 42; i++) {
      _calender.add(DateTime(_viewMonth.year, _viewMonth.month, i - startWeekDay));
    }
  }

  void goBackMonth() {
    _viewMonth = DateTime(_viewMonth.year, _viewMonth.month - 1, 1);

    viewCalender();
  }

  void goFrontMonth() {
    _viewMonth = DateTime(_viewMonth.year, _viewMonth.month + 1, 1);

    viewCalender();
  }

  void selectDate(DateTime date) {
    if (_selectedDateList.indexOf(date) == -1) {
      _selectedDateList.add(date);
    } else {
      _selectedDateList.remove(date);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(
        horizontal: 12,
        vertical: 30,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.all(
          Radius.circular(20),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              GestureDetector(
                onTap: () {
                  goBackMonth();
                  setState(() {});
                },
                child: Container(
                  padding: EdgeInsets.all(10),
                  child: Icon(
                    Icons.arrow_back_ios,
                  ),
                ),
              ),
              Text(
                _viewMonth.month < 10 ? '${_viewMonth.year}.0${_viewMonth.month}' : '${_viewMonth.year}.${_viewMonth.month}',
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  fontStyle: FontStyle.italic,
                ),
              ),
              GestureDetector(
                onTap: () {
                  goFrontMonth();
                  setState(() {});
                },
                child: Container(
                  padding: EdgeInsets.all(10),
                  child: Icon(
                    Icons.arrow_forward_ios,
                  ),
                ),
              ),
            ],
          ),
          SizedBox(
            height: 20,
          ),
          Row(
            children: [
              ...List.generate(
                7,
                (index) => Expanded(
                  child: Text(
                    weekNames[index],
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                      fontStyle: FontStyle.italic,
                      color: index == 0
                          ? Colors.red
                          : index == 6
                              ? Colors.blue
                              : Colors.black,
                    ),
                  ),
                ),
              ),
            ],
          ),
          SizedBox(
            height: 12,
          ),
          Divider(
            height: 0,
            thickness: 1,
            color: Colors.black12,
          ),
          SizedBox(
            height: 12,
          ),
          GestureDetector(
            onHorizontalDragStart: (details) {
              goFrontMonth();
              setState(() {});
            },
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                ...List.generate(
                  6,
                  (index1) => Padding(
                    padding: EdgeInsets.only(bottom: 4),
                    child: Row(
                      children: [
                        ...List.generate(
                          7,
                          (index2) => Expanded(
                            child: GestureDetector(
                              onTap: _calender[index1 * 7 + index2].month != _viewMonth.month
                                  ? () {}
                                  : () {
                                      widget.selectDate!(_calender[index1 * 7 + index2]);
                                      selectDate(_calender[index1 * 7 + index2]);
                                      setState(() {});
                                    },
                              child: Container(
                                padding: EdgeInsets.all(12),
                                decoration: BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: _selectedDateList.indexOf(_calender[index1 * 7 + index2]) != -1 ? Colors.greenAccent : Colors.transparent,
                                ),
                                child: Text(
                                  _calender[index1 * 7 + index2].day.toString(),
                                  textAlign: TextAlign.center,
                                  style: TextStyle(
                                    fontWeight: _selectedDateList.indexOf(_calender[index1 * 7 + index2]) != -1 ? FontWeight.bold : FontWeight.w500,
                                    fontSize: 16,
                                    fontStyle: FontStyle.italic,
                                    color: _calender[index1 * 7 + index2].month != _viewMonth.month
                                        ? Colors.grey
                                        : _selectedDateList.indexOf(
                                                  _calender[index1 * 7 + index2],
                                                ) !=
                                                -1
                                            ? Colors.white
                                            : _calender[index1 * 7 + index2].weekday == 6
                                                ? Colors.blue
                                                : _calender[index1 * 7 + index2].weekday == 7
                                                    ? Colors.red
                                                    : Colors.black,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

처음 코드를 작성할 때는 달력의 날짜만큼 배열에 담고 for문을 돌렸는데 시작 날짜가 토요일이나 일요일 부터 시작하는 날에는 총 6주가 되어 달력의 크기가 달라지는게 거슬려서 6주로 고정하고 비어있는 곳은 전달의 마지말 날들과 다음 달의 시작 날들로 처리해주었습니다.

구현하면서 힘들었던 점은 상태관리를 사용하지않고 구현했는데 상태관리를 쓰지 않으니 부모와 자식의 state를 컨트롤 하는게 조금 까다로웠습니다. 결국 해결하지 못하여서 부모에게서 받은 함수와 자식 자체의 함수를 하나 더 선언하여 date를 선택 할때마다 두번의 함수를 실행하게 하였습니다.

다음 포스팅에서는 상태관리를 적용하고 기능과 디자인을 좀 보완해서 포스팅 해보겠습니다.

profile
Flutter 개발자

0개의 댓글