Android/Calendar

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

O_Gyong 2025. 7. 11.
 

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

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/Calendargithub.com캘린

ogyong.tistory.com

지난번 kizitonwose/Calendar, 캘린더 라이브러리 사용기 #1에 이어서 몇 가지 기능을 추가해 보려고 한다.

- CalendarView 가 나타내고 있는 연, 월 표시
- 요일을 표시할 때 일요일과 토요일에 다른 컬러 주기
- 날짜 제한
- 날짜 View 이외에 작업량과 같은 데이터 표시

 

우선 결과 화면은 아래와 같다.

(왼) 수정 전 / (오) 수정 후

 


CalendarView 가 나타내고 있는 연, 월 표시

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

        <TextView
            android:id="@+id/tv_title_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textColor="@color/colorDarkGray"
            android:textStyle="bold"
            android:textSize="18sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            tools:text="2025년 07월" />

        <com.kizitonwose.calendar.view.CalendarView
            android:id="@+id/calendarView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            app:layout_constraintTop_toBottomOf="@id/tv_title_date"
            app:cv_dayViewResource="@layout/calendar_day_layout"
            app:cv_monthHeaderResource="@layout/calendar_day_titles_container" />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/colorDarkGray"
            android:layout_marginTop="2dp"
            android:layout_marginHorizontal="15dp"
            app:layout_constraintTop_toBottomOf="@id/calendarView"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

우선 activity_main에서 연도와 월을 표시할 TextView와 CalendarView를 구분해 줄 View를 하나 넣었다. CalendarView가 스크롤이 되면 바뀐 연도와 월에 맞춰 변경을 해줘야 한다.

 

    // MainActivity
    private fun setListener() {
        binding.calendarView.monthScrollListener = { month ->
            binding.tvTitleDate.text = month.yearMonth.toString()
        }
        
        // 생략
    }

kizitonwoze 라이브러리에는 monthScrollListener가 있다. monthScrollListener는 캘린더가 새 달로 스크롤이 될 때 호출이 된다. 위의 리스너만 등록해 주면 CalendarView가 나타내고 있는 연도와 월을 표시할 수 있게 된다.

 


요일을 표시할 때 일요일과 토요일에 다른 컬러 주기

    // MainActivity
    private fun setListener() {
        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())
                            if (title == "일") {
                                textView.setTextColor(getColor(R.color.colorRed))
                            } else if (title == "토") {
                                textView.setTextColor(getColor(R.color.colorBlue))
                            }

                            textView.text = title
                        }
                }
            }
        }
        
        // 생략
    }

MonthHeaderFooterBinder에서 요일에 대한 처리를 하는데, 이곳에서 일요일과 토요일에 대한 textColor를 설정했다.

 


 

날짜 제한

class DayViewContainer(view: View): ViewContainer(view) {
    val binding = CalendarDayLayoutBinding.bind(view)
    val containerView = binding.clContainer
    val dayView = binding.calendarDayText
}

DayViewContainer에 DataBinding을 적용해주고, 변수명도 수정했다.

 

    // MainActivity
    private lateinit var today: LocalDate

    override fun onCreate(savedInstanceState: Bundle?) {
        today = LocalDate.now()
        // 생략
    }
    
    private fun setListener() {
        binding.calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
            override fun create(view: View) = DayViewContainer(view)
            override fun bind(container: DayViewContainer, data: CalendarDay) {
                val containerView = container.containerView
                val dayView = container.dayView
                dayView.text = data.date.dayOfMonth.toString()

                // 60일 전과 오늘 이후 날짜 사용 금지
                val agoDate = today.minusDays(60)
                val useDate = !data.date.isBefore(agoDate) && !data.date.isAfter(today)

                if(useDate) {
                    containerView.visibility = View.VISIBLE

                    if (data.date == selectedDate) {
                        dayView.setTextColor(Color.WHITE)
                        containerView.setBackgroundResource(R.drawable.selection_background)
                    } else {
                        dayView.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 {
                    dayView.setTextColor(getColor(R.color.colorGray))
                }
            }
        }
        
        // 생략
    }

CalendarView의 날짜와 관련된 부분은 MonthDayBinder에서 처리를 하게 된다. bind 블록에서 60일 전과 오늘 이후의 날짜에 대해 사용할 수 없도록 처리를 했다. 임시로 60일을 기준으로 잡았다. 

 

위의 작업은 날짜 View에 대한 처리만 되어 CalendarView가 제한 범위 밖으로 스크롤이 된다. CalendarView의 월 표시 제한은 CalendarView를 처음 설정할 때 사용했던 minusMonths와 plusMonths에서 해야 한다.

 

	// MainActivity
	private fun setCalendar() {
        val currentMonth = YearMonth.now()
        val startMonth = currentMonth.minusMonths(2)
        val endMonth = currentMonth.plusMonths(0)
        val daysOfWeek = daysOfWeek()
        binding.calendarView.setup(startMonth, endMonth, daysOfWeek.first())
        binding.calendarView.scrollToMonth(currentMonth)
    }

minusMonths를 통해 두 달만 이동할 수 있게 하고, plusMonths를 통해 오늘 이후의 월은 이동할 수 없게 했다.

 


날짜 View 이외에 작업량과 같은 데이터 표시

data class SampleData(
    val total: Int,
    val date: String
)

위와 같이 수량(total)과 날짜(date)를 갖는 데이터가 있을 때 날짜마다 total 값을 함께 표시하려고 한다.

 

	// calendar_day_layout
	<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="wrap_content"
            android:gravity="center"
            android:textSize="16sp"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="22" />

        <TextView
            android:id="@+id/tv_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="11sp"
            android:textStyle="bold"
            android:textColor="@color/colorPurple"
            android:gravity="center"
            app:layout_constraintTop_toBottomOf="@id/calendarDayText"
            tools:text="5건" />
    </androidx.constraintlayout.widget.ConstraintLayout>
class DayViewContainer(view: View): ViewContainer(view) {
    val binding = CalendarDayLayoutBinding.bind(view)
    val containerView = binding.clContainer
    val dayView = binding.calendarDayText
    val countView = binding.tvCount
}

날짜 이외에 수량 값을 표시하기 위해 calendar_day_layout에서 수량을 표시할 TextView를 추가한다. 그리고 DayViewContainer에도 countView를 추가한다.

 

	// MainActivity
	private fun setListener() {
        binding.calendarView.dayBinder = object : MonthDayBinder<DayViewContainer> {
            override fun create(view: View) = DayViewContainer(view)
            override fun bind(container: DayViewContainer, data: CalendarDay) {
                val containerView = container.containerView
                val dayView = container.dayView
                val countView = container.countView

                // 60일 전과 오늘 이후 날짜 사용 금지
                val agoDate = today.minusDays(60)
                val useDate = !data.date.isBefore(agoDate) && !data.date.isAfter(today)

                dayView.text = data.date.dayOfMonth.toString()

                if(useDate) {
                    containerView.visibility = View.VISIBLE

                    val count = sampleList.find { it.date == data.date.toString() }?.total?:0
                    countView.text = "${count}건"

                    if (data.date == selectedDate) {
                        dayView.setTextColor(getColor(R.color.white))
                        countView.setTextColor(getColor(R.color.white))
                        containerView.setBackgroundResource(R.drawable.selection_background)
                    } else {
                        dayView.setTextColor(getColor(R.color.black))
                        countView.setTextColor(getColor(R.color.colorPurple))
                        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 {
                    dayView.setTextColor(getColor(R.color.colorGray))
                    countView.setTextColor(getColor(R.color.colorGray))
                }
            }
        }
        
        // 생략
    }

MonthDayBinder에서 DayViewContainer에 선언한 countView의 텍스트를 설정해 준다. useData 블록 내에서 sampleList의 날짜와 CalendarView의 날짜와 일치하는 값을 찾아서 적용하면 된다. 추가로 클릭했을 때와 제한 범위를 고려하여 텍스트 색상 등을 적용했다.

 

 

 

전체 코드

 

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

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

github.com

 

댓글