[Android] kizitonwose/Calendar, 캘린더 라이브러리 사용기 #1

2025. 7. 8. 09:58·Android/Calendar
반응형

 

 

GitHub - kizitonwose/Calendar: A highly customizable calendar view and compose library for Android and Kotlin Multiplatform.

A highly customizable calendar view and compose library for Android and Kotlin Multiplatform. - kizitonwose/Calendar

github.com

캘린더에 날짜 이외에 다른 데이터를 함께 표시할 일이 생겨서 적합한 라이브러리를 찾던 중 kizitonwose의 Calendar 라이브러리를 발견했다.

 

해당 라이브러리는 캘린더를 개발자가 원하는 모습으로 꾸미는 것이 가능하다. 그리고 라이브러리를 사용하기 위한 샘플을 제공하고, 함수에 대한 정리 문서도 잘 되어 있어서 적용을 하는데 굉장히 편하다는 느낌이 들었다.


◾ 의존성 등록

libs.version.toml

[versions]
kizitonwoseCalendar = "2.6.0"

[libraries]
kizitonwose-calendar = { module = "com.kizitonwose.calendar:view", version.ref = "kizitonwoseCalendar" }

라이브러리를 사용하기 위해서 버전 카탈로그에 위와 같이 등록해 준다.


◾ 캘린더 날짜 표시하기

activity_main.xml

<com.kizitonwose.calendar.view.CalendarView
    android:id="@+id/calendarView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"
    app:cv_dayViewResource="@layout/calendar_day_layout" />

라이브러리에서 '주' 단위 달력인 WeekCalednarView, '연도' 단위 달력인 YearCaledarView 그리고 '월' 기반의 CalendarView가 있다. 월별 표시를 위해서 CalendarView를 사용했다.

 

CalendarView를 보면 cv_dayViewResource가 있는데, 이 속성에 날짜 View 리소스를 넣어야 한다. 날짜 리소스는 직접 만들어서 사용하기 때문에 개발자가 표현하고자 하는 모습으로 꾸미는 것이 가능하다.

 

calendar_day_layout.xml

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/cl_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="blocksDescendants"
    android:paddingVertical="5dp">

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

우선 캘린더는 간단하게 일자만 표시하도록 했다. 

 

DayViewContainer.kt

class DayViewContainer(view: View) : ViewContainer(view) {
    val textView = view.findViewById<TextView>(R.id.calendarDayText)
}

MainActivity.kt

binding.calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
    override fun create(view: View) = DayViewContainer(view)
    override fun bind(container: DayViewContainer, data: CalendarDay) {
        container.textView.text = data.date.dayOfMonth.toString()
    }
}

CalendarView는 RecyclerView로 구현이 되어 있는데, 방금 만든 날짜 리소스를 뷰 홀더에 전달할 필요가 있다. ViewContainer가 뷰 홀더 역할을 하게 된다.

 

CalendarView를 사용하기 위해 필요한 DayViewContainer 유형을 MonthDayBinder를 통해 제공한다. 즉, CalendarView는 monthDayBinder를 통해 DayViewContainer를 생성하고, 각 날짜 항목에 CalendarDay 데이터를 바인딩하여 화면에 표시한다.

 

MainActivity.kt

private fun setCalendar() {
    val currentMonth = YearMonth.now()
    val startMonth = currentMonth.minusMonths(100) // 과거 100달
    val endMonth = currentMonth.plusMonths(100) // 미래 100달
    val firstDayOfWeek = firstDayOfWeekFromLocale()

    binding.calendarView.setup(startMonth, endMonth, firstDayOfWeek)
    binding.calendarView.scrollToMonth(currentMonth)
    
    // 생략
}

마지막으로 CalendarView에 표시할 날짜를 설정하면 캘린더가 그려진다.

 


◾ 캘린더 요일 표시하기

위의 과정에서 CalendarView에 날짜를 표시했는데, 이번에는 요일 표시를 하려고 한다. 요일을 표시하는 방법으로 첫 번째는 달력 위에 고정된 요일 View를 넣는 것이고, 두 번째는 CalendarView의 cv_monthHeaderResource 속성을 사용하여 정적이지 않고 스크롤이 되도록 표시할 수도 있다.

 

val daysOfWeek = daysOfWeek()

val daysOfWeek = daysOfWeek(firstDayOfWeek = DayOfWeek.THURSDAY)

요일을 표시할 때 daysOfWeek()를 사용하는데, daysOfWeek()는 현재 요일을 생성하는 함수로 기본적으로 '일, 월, 화, 수, 목, 금, 토' 순서로 설정이 되어 있는 거 같다. 만약 요일의 순서를 변경하고 싶은 경우 firstDayOfWeek에 값을 설정하여 변경할 수 있다.

 

calendar_day_title_text.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:gravity="center" />

caledar_day_titles_container.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:weightSum="7"
    android:orientation="horizontal">
    <include layout="@layout/calendar_day_title_text" />
    <include layout="@layout/calendar_day_title_text" />
    <include layout="@layout/calendar_day_title_text" />
    <include layout="@layout/calendar_day_title_text" />
    <include layout="@layout/calendar_day_title_text" />
    <include layout="@layout/calendar_day_title_text" />
    <include layout="@layout/calendar_day_title_text" />
</LinearLayout>

요일을 표시하기 위한 View를 그린다. TextView 타입을 정해놓고 container에서 재사용할 수 있게 했다.


1. 정적 View로 그리기

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <include
        android:id="@+id/titlesContainer"
        layout="@layout/calendar_day_titles_container" />
    <com.kizitonwose.calendar.view.CalendarView
        android:id="@+id/calendarView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toBottomOf="@id/titlesContainer"
        app:cv_dayViewResource="@layout/calendar_day_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

요일 View를 CalendarView 위에 표시한다.

 

MainActivity.kt

val titlesContainer = binding.titlesContainer as ViewGroup
titlesContainer.children
    .map { it as TextView }
    .forEachIndexed { index, textView ->
        val dayOfWeek = daysOfWeek[index]
        val title = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
        textView.text = title
    }

daysOfWeek의 요일 값을 하나씩 매칭시켜주면 요일 설정이 끝난다.

※ 위의 방법을 쓰지 않고 요일 '일~월' 요일 View를 그려서 표시해도 된다.


2. 동적 View로 그리기

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.kizitonwose.calendar.view.CalendarView
        android:id="@+id/calendarView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:cv_dayViewResource="@layout/calendar_day_layout"
        app:cv_monthHeaderResource="@layout/calendar_day_titles_container" />
</androidx.constraintlayout.widget.ConstraintLayout>

CalendarView의 cv_monthHeaderResource 속성에 요일 리소스를 넣어준다.

 

MonthViewContainer.kt

class MonthViewContainer(view: View) : ViewContainer(view) {
    val titlesContainer = view as ViewGroup
}

MainActivity.kt

binding.calendarView.monthHeaderBinder = object : MonthHeaderFooterBinder<MonthViewContainer> {
    val daysOfWeek = daysOfWeek()
    override fun create(view: View) = MonthViewContainer(view)
    override fun bind(container: MonthViewContainer, data: CalendarMonth) {
        if (container.titlesContainer.tag == null) {
            container.titlesContainer.tag = data.yearMonth
            container.titlesContainer.children.map { it as TextView }
                .forEachIndexed { index, textView ->
                    val dayOfWeek = daysOfWeek[index]
                    val title = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
                    textView.text = title
                }
        }
    }
}

날짜를 표시했을 때와 마찬가지로 뷰홀더에 요일 View 리소스를 전달해야 하고, MonthHeaderFooterBinder를 통해 MonthViewContainer를 생성한다.

 

MonthViewContainer의 bind 블록에서 daysOfWeek의 요일 값을 하나씩 매칭시켜 주면 요일 설정이 끝난다.


제일 상단에 표시되는 요일은 첫 번째 정적으로 그린 것이고, 두 번째로 표시되는 요일은 동적으로 그린 것이다. 선호하는 동작에 맞춰 선택하면 될 것 같다.


◾ 날짜 선택(클릭)

문서를 보면 클릭 이벤트를 처리할 때 Activity나 Fragment에서 클릭 리스너를 구현하라고 되어있다. 그래서 ViewContainer가 Activity나 Fragment 내에서 선언이 된 것 같은데, 나는 ViewContainer를 분리해서 사용하고 싶어서 bind 블록 내에서 모두 처리했다.

 

DayViewContainer.kt

class DayViewContainer(view: View): ViewContainer(view) {
    val containerView = view.findViewById<ConstraintLayout>(R.id.cl_container)
    val textView = view.findViewById<TextView>(R.id.calendarDayText)
    lateinit var day: CalendarDay
}

DayViewContainer에 클릭을 위한 containerView와 day 값을 추가한다. 

 

MainActivity.kt

private var selectedDate: LocalDate? = null

binding.calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
    override fun create(view: View) = DayViewContainer(view)
    override fun bind(container: DayViewContainer, data: CalendarDay) {
        val textView = container.textView
        val containerView = container.containerView
        textView.text = data.date.dayOfMonth.toString()

        if (data.position == DayPosition.MonthDate) {
            containerView.visibility = View.VISIBLE

            if (data.date == selectedDate) {
                textView.setTextColor(Color.WHITE)
                containerView.setBackgroundResource(R.drawable.selection_background)
            } else {
                textView.setTextColor(Color.BLACK)
                containerView.background = null
            }

            containerView.setOnClickListener {
                val currentSelection = selectedDate
                if (currentSelection == data.date) {
                    selectedDate = null
                    binding.calendarView.notifyDateChanged(currentSelection)
                } else {
                    selectedDate = data.date
                    binding.calendarView.notifyDateChanged(data.date)
                    if (currentSelection != null) {
                        binding.calendarView.notifyDateChanged(currentSelection)
                    }
                }
            }
        } else {
            containerView.visibility = View.INVISIBLE
        }
    }
}

DayPosition은 MonthDate, InDate, OutDate가 있다. data.position으로 해당 값을 확인할 수 있다.

- MonthDate: 현재 표시되는 날짜 중 현재 달의 날짜
- InDate: 현재 표시되는 날짜 중 이전 달의 날짜
- OutDate: 현재 표시되는 날짜 중 다음 달의 날짜

 

CalendarView에 표시되는 데이터 중 MonthDate인 값만 표시 및 클릭 이벤트 처리를 해준다. 그리고 이 라이브러리에는 날짜 선택을 했다는 것에 대한 상태값이 없기 때문에 selectedDate 필드 변수를 추가하여 클릭한 날짜에 대해서만 강조 표시를 하고, 다시 클릭했을 때 해제할 수 있도록 한다.


※ 프로젝트 설정
minSdk: 26
compileSdk, targetSdk: 35
gradle: 8.14.2
java: JavaVersion.VERSION_1_8 (ver 8)

 

전체 코드

 

Android_Study/Calendar by Kizitonwose at calendar-by-kizitonwose-step1 · OhGyong/Android_Study

안드로이드 개발 공부. Contribute to OhGyong/Android_Study development by creating an account on GitHub.

github.com

반응형

'Android > Calendar' 카테고리의 다른 글

[Android][Compose] kizitonwose/Calendar, 캘린더 라이브러리 사용기 #3  (3) 2025.07.14
[Android] kizitonwose/Calendar, 캘린더 라이브러리 사용기 #2  (6) 2025.07.11
'Android/Calendar' 카테고리의 다른 글
  • [Android][Compose] kizitonwose/Calendar, 캘린더 라이브러리 사용기 #3
  • [Android] kizitonwose/Calendar, 캘린더 라이브러리 사용기 #2
O_Gyong
O_Gyong
안드로이드 기술 정리
  • O_Gyong
    O_Gyong's TECH
    O_Gyong
    • 분류 전체보기 (79)
      • Android (56)
        • ADB (4)
        • AddView (1)
        • Architecture (1)
        • Bluetooth (2)
        • BuildConfig (1)
        • Calendar (3)
        • Camera (2)
        • Cipher (1)
        • Compose (1)
        • ConstraintSet (1)
        • DataStore (1)
        • Dialog (1)
        • DrawerLayout (1)
        • Flow (1)
        • Glide (1)
        • MQTT (2)
        • Paging3 (4)
        • Permission (1)
        • SharedPreferences (3)
        • RecyclerView (5)
        • Room (1)
        • Splash (1)
        • TabLayout (2)
        • TextWatcher (1)
        • Update (1)
        • 이슈 처리 (13)
      • Android Studio (5)
      • Firebase (1)
      • Git (3)
      • 작업 일지 (13)
  • 최근 글

  • 인기 글

  • 태그

    GIT
    firebase
    Kotlin
    issue
    recyclerview
    CameraX
    hilt
    MQTT
    Bluetooth
    in-app update
    Paging3
    loading
    BLE
    Andoird
    webview
    Android Studio
    compose
    kizitonwose
    TabLayout
    ADB
    paging
    flow
    SharedPreferences
    해상도
    Android
    Navigation
    CalendarView
    Pagination
    Room
    github
  • 링크

    • GitHub
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • hELLO· Designed By정상우.v4.10.4
O_Gyong
[Android] kizitonwose/Calendar, 캘린더 라이브러리 사용기 #1
상단으로

티스토리툴바