ANDROID/Android 개발 이슈 & 해결

[Android] CalendarView 라이브러리 추천 : kizitonwose/CalendarView

주 녕 2022. 4. 5. 18:27
728x90

프로젝트에서 캘린더가 필수적인 기능이었기 때문에

여러 캘린더 라이브러리를 들여다보고 뜯고 맛보고 즐기고(?)...

원하는 기능이 라이브러리에 없는 경우, 라이브러리를 뜯어서 해결하다가 그래도 안되는 기능이 있어서
3번 정도 캘린더 라이브러리를 갈아엎고 정착한 라이브러리에 대해 소개하고자 한다!

 

 

kizitonwose/CalendarView

 

GitHub - kizitonwose/CalendarView: A highly customizable calendar library for Android, powered by RecyclerView.

A highly customizable calendar library for Android, powered by RecyclerView. - GitHub - kizitonwose/CalendarView: A highly customizable calendar library for Android, powered by RecyclerView.

github.com

이 라이브러리의 장점이라고 하면 다양한 Sample 코드를 제공하고 README에서 상세하게 설명해준 다는 것이다!

README의 코드를 어느 정도 이해했다면 필요한 기능은 대부분 Sample 코드에서 찾아 이해할 수 있다👍

 

내가 사용한 버전은 다음과 같다.

implementation 'com.github.kizitonwose:CalendarView:1.0.4'

해당 캘린더의 RecyclerView를 기반으로 하고 있다.

 

 

1. CalendarView에서 사용할 cell 정의하기

res/layout/calendar_day_layout.xml

<TextView
    android:id="@+id/calendarDayText"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="16sp"
    tools:text="22" />

이 코드는 Readme의 샘플 코드를 가져온 것이다.

날짜마다 이미지를 넣어야 하거나 점을 찍어야 한다면 이 곳에서 <ImageView> 등으로 처리할 수 있다.

 

2. CalendarView 추가하기

    <com.kizitonwose.calendarview.CalendarView
        android:id="@+id/calendar_view"
        android:layout_width="0dp"
        android:layout_height="504dp"
        android:layout_marginBottom="20dp"
        app:cv_orientation="horizontal"
        app:cv_hasBoundaries="true"
        app:cv_scrollMode="paged"
        app:cv_dayViewResource="@layout/calendar_day_layout"/>

캘린더 뷰를 띄워야 하는 Activity나 Fragment에 <com.kizitonwose.calendarview.CalendarView>로 추가할 수 있다.

CalendarView 라이브러리에서 사용할 수 있는 속성은 다음과 같다.

  • cv_dayViewResource : 날짜 cell의 레이아웃 파일 연결
  • cv_orientation : 캘린더의 방향은 horizontal(수평), vertical(수직, default) 중 결정할 수 있다.
  • cv_scrollMode : 캘린더의 스크롤모드 paged, continuous(default) 중 결정할 수 있다.
  • cv_maxRowCount : 각 월의 row의 최대값을 지정할 수 있다. (한 달이 최대 6주차까지 있을 수 있으니 maxRowCount=6)
  • cv_hasBoundaries 
    • true : 월 섹션에 해당 월과 inDates, outDates를 포함할 수 있다.
    • false : 날짜는 월 섹션에 관계없이 계속 추가될 수 있다.

  • cv_inDateStyle : 녹색 부분, 즉 이전 달을 어떻게 나타낼 것인지 결정한다.
    • allMonths, firstMonth로 값을 지정하면 inDate가 나타나고, none으로 지정한다면 나타나지 않는다.
  • cv_outDateStyle : 빨간색 부분, 즉 다음 달을 어떻게 나타낼 것인이 결정한다.
    • endOfRowendOfGrid로 값을 지정하면 outDate가 나타난다.
    • endOfGrid로 지정했다면, maxRowCount를 만족할 때까지 outDate가 나타난다. (5주차까지 있는 달인데 maxRowCount가 6이라면 6번째 row까지 outDate(= 다음달)이 나타나는 것)

 

3. 캘린더 세팅하기

Cell 레이아웃의 위젯을 사용하기 위해서 해당 클래스를 만들어서 연결한다.

    class DayViewContainer(view: View) : ViewContainer(view) {
        var date = CalendarDayLayoutBinding.bind(view).calendarDayText
        var today = CalendarDayLayoutBinding.bind(view).ivTodayDot
        var doneIcon = CalendarDayLayoutBinding.bind(view).doneIcon
    }

아래는 내가 작성한 코드의 일부이다.

  • 해당 라이브러리는 recyclerview를 기반으로 만들어졌기 때문에 데이터의 변화가 생겼을 때 깜빡임이 발생했다.
    기존의 recyclerview에서 깜빡임을 제거한 것과 동일한 방식으로 itemAnimator = null 처리를 했다.
  • 해당 라이브러리는 cell의 크기가 고정되어 있다.
    따라서 cell의 레이아웃만큼 늘어나지 않고 잘려서 나오기 때문에 daySize로 cell의 크기를 변경해주었다.
  • firstMonth와 lastMonth는 전체 캘린더의 크기(월 수)를 지정하는 것이다.
  • firstDayOfWeek은 캘린더의 시작 요일을 지정하는 변수이다.
  • dayBinder에서 위에서 선언한 Cell 레이아웃을 연결한 클래스를 사용한다.
    • bind() 메소드에서 해당 날짜별로 처리하고 싶은 액션을 지정해주면 된다.
    • DayOwner 클래스의 PREVIEW_MONTH, NEXT_MONTH, THIS_MONTH를 통해서 inDate, outDate, 이번 달을 처리할 수 있다.
private fun setCalendarView() {
        with(binding.calendarView) {
            itemAnimator = null  // 깜빡임 제거
            doOnPreDraw {
                daySize = Size(binding.calendarView.width/7, cell의 height)
            }

            val currentMonth = YearMonth.now()
            val firstMonth = currentMonth.minusMonths(240)
            val lastMonth = currentMonth.plusMonths(240)
            val firstDayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
            setup(firstMonth, lastMonth, firstDayOfWeek)
            scrollToMonth(currentMonth)
            
            // 연결한 cell을 날짜에 따라 처리
            dayBinder = object : DayBinder<DayViewContainer> {
                override fun create(view: View) = DayViewContainer(view)
                override fun bind(container: DayViewContainer, day: CalendarDay) {
                    // 날짜 클릭 이벤트
                    val intent = Intent(this@MainActivity, DoneActivity::class.java)
                    container.view.setOnClickListener {
                        val clickedDate = "${day.date.year}-${df.format(day.date.monthValue)}-${df.format(day.date.dayOfMonth)}"
                        intent.putExtra("clickedDate", clickedDate)
                        startActivity(intent)
                    }

                    // 각 셀마다 날짜 지정
                    container.date.text = day.date.dayOfMonth.toString()

                    if (day.owner != DayOwner.THIS_MONTH) {
                        // 현재 날짜가 이번 달의 날짜가 아닌 경우
                        container.date.setTextColor(ContextCompat.getColor(context, R.color.calendarHiddenColor))
                        ...
                        if (day.date.isAfter(today)) {
                            ...
                        }
                    } else {
                        // 이번 달의 날짜인 경우
                        // '오늘' 체크
                        when (day.date) {
                            today -> {
                                // today = LocalDate.now()
                                container.date.typeface = resources.getFont(R.font.spoqa_han_sans_neo_bold)
                                container.date.setTextColor(Color.BLACK)
                                container.today.visibility = View.VISIBLE
                            }
                            else -> {
                                ...
                            }
                        }
                        ...
                    }
                }
            }
        }
    }

 

 

 

캘린더 커스텀이 어렵다고 들어서 여러 캘린더 라이브러리를 살펴보게 되었다.

블로그에 따로 포스팅하지는 않겠지만 여러 라이브러리를 살펴보고, 실제로 라이브러리를 수정해서 사용하기 위해 모듈로 import해서 커스텀을 해보는 경험도 하였다 (물론 최종적으로는 다른 라이브러리를 사용했지만ㅎㅎ) 

 

혹시 해당 라이브러리를 사용하다 궁금한 점이 생긴다면 issue를 적극 활용해보시고,

Sample 코드도 너무 친절하니 한번 찬찬히 살펴보시길! 그래도 의문이 생긴다면 댓글로 같이 고민해봅시다!

728x90