본문 바로가기

Kotlin_study

[Kotlin] Custom Calendar Library(kizitonwose) 사용하기

## 다이어리 프로젝트 with SandBurger

[Kotlin] CosmoCalendar library 사용하기 (tistory.com)

지난 포스팅에서 cosmocalendar 라이브러리를 사용하여 calendar를 커스텀하는 작업을 진행했습니다. 하지만 커스텀하는 과정에서 저희 프로젝트에서 원하는만큼의 커스텀이 어렵다고 판단하여 새로운 라이브러리를 찾게 되었고, 그 결과 kizitonwose CalendarView 라는 라이브러리를 사용하게 되었습니다.

 

완성된 화면은 아래와 같습니다

CalendarFragment.kt

class DayViewContainer(view : View) : ViewContainer(view) {
            val textView = ItemCalendarDayBinding.bind(view).itemCalendarDayTv
            val imageView = ItemCalendarDayBinding.bind(view).itemCalendarDayIv
            lateinit var day : CalendarDay
            init {
            //날짜 click시 동작
                view.setOnClickListener{
                    if (day.owner == DayOwner.THIS_MONTH){
                        val currentSelection = selectedDay
                        // 이미 선택한 날짜일 시 선택 취소
                        if(currentSelection == day.date){
                            selectedDay = null
                            binding.calendarCalendarCv.notifyDateChanged(currentSelection)
                        } else {
                        // 새로 선택한 날짜일 시 새로 선택한 날짜로 변경
                            selectedDay = day.date
                            binding.calendarCalendarCv.notifyDateChanged(day.date)
                            if (currentSelection != null){
                                binding.calendarCalendarCv.notifyDateChanged(currentSelection)
                            }
                        }
                    }
                }
            }
        }
        
        binding.calendarCalendarCv.dayBinder = object : DayBinder<DayViewContainer> {
                override fun create(view: View) =  DayViewContainer(view)
                override fun bind(container: DayViewContainer, day: CalendarDay) {
                    container.day = day
                    container.textView.text = day.date.dayOfMonth.toString()
                    if(day.owner == DayOwner.THIS_MONTH){
                        when{
                        	// 선택된 날짜는 백그라운드 이미지 보이게 설정
                            day.date == selectedDay -> {
                                container.imageView.visibility = View.VISIBLE
                            }
                            // 일요일 색깔 변경
                            day.date.dayOfWeek.value == 7 -> {
                                container.textView.setTextColor(ContextCompat
                                    .getColor(requireContext(), R.color.error))
                            }
                            // 토요일 색깔 변경
                            day.date.dayOfWeek.value == 6 -> {
                                container.textView.setTextColor(ContextCompat
                                    .getColor(requireContext(), R.color.saturday_blue))
                            }
                            else -> {
                                container.textView.setTextColor(ContextCompat
                                        .getColor(requireContext(), R.color.line_black))
                                container.imageView.visibility = View.INVISIBLE
                            }
                        }
                        // 현재 선택된 월이 아니면 날짜 색깔을 회색으로 표시
                    } else {
                        container.textView.setTextColor(ContextCompat
                            .getColor(requireContext(),R.color.line_grey))
                    }
                }
                val daysOfWeek = arrayOf(
                DayOfWeek.SUNDAY,
                DayOfWeek.MONDAY,
                DayOfWeek.TUESDAY,
                DayOfWeek.WEDNESDAY,
                DayOfWeek.THURSDAY,
                DayOfWeek.FRIDAY,
                DayOfWeek.SATURDAY
            )

            val currentMonth = YearMonth.now()
            val firstMonth = currentMonth.minusMonths(10)
            val lastMonth = currentMonth.plusMonths(10)
            binding.calendarCalendarCv.setup(firstMonth, lastMonth, daysOfWeek.first())
            binding.calendarCalendarCv.scrollToMonth(currentMonth)
        }

FragmentCalendarDate.kt

class FragmentCalendarDate : Fragment() {
    private var _binding: FragmentCalendarDateBinding? = null
    private val binding get() = _binding!!
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentCalendarDateBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

fragment_calendar.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/backgroundColor"
    tools:context=".ui.calendar.CalendarFragment">
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/calendar_date_container"
        android:layout_width="0dp"
        android:layout_height="30dp"
        android:name="com.example.sandiary.ui.calendar.FragmentCalendarDate"
        app:layout_constraintTop_toBottomOf="@id/calendar_divider_iv"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    <com.kizitonwose.calendarview.CalendarView
        android:id="@+id/calendar_calendar_cv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cv_orientation="horizontal"
        app:cv_inDateStyle="allMonths"
        app:cv_outDateStyle="endOfRow"
        app:cv_scrollMode="paged"
        app:cv_hasBoundaries="true"
        app:cv_dayViewResource="@layout/item_calendar_day"
        app:layout_constraintTop_toBottomOf="@id/calendar_date_container"
        app:layout_constraintStart_toStartOf="parent"/>
    <ImageView
        android:id="@+id/calendar_divider_iv"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:src="@color/brand_light"
        android:layout_marginBottom="17dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        />
    <TextView
        android:id="@+id/see_all_date_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textStyle="bold"
        android:textColor="@color/background_white"
        android:fontFamily="@font/spoqa_han_sans_neo_medium"
        android:text="5월 15일"
        android:layout_marginTop="33dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    <ImageView
        android:id="@+id/calendar_plan_background_iv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:src="@color/background_white"
        app:layout_constraintTop_toBottomOf="@id/calendar_calendar_cv"
        app:layout_constraintBottom_toBottomOf="parent"/>
    <TextView
        android:id="@+id/calendar_today_plan_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/today_plan"
        android:textSize="14sp"
        android:textColor="@color/font_black"
        android:layout_marginTop="15dp"
        android:layout_marginStart="15dp"
        android:fontFamily="@font/spoqa_han_sans_neo_regular"
        app:layout_constraintTop_toTopOf="@id/calendar_plan_background_iv"
        app:layout_constraintStart_toStartOf="parent"/>
    <ImageView
        android:id="@+id/calendar_plan_dot_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/grey_dot"
        android:layout_marginTop="26dp"
        app:layout_constraintTop_toBottomOf="@id/calendar_today_plan_tv"
        app:layout_constraintStart_toStartOf="@id/calendar_today_plan_tv"/>
    <ImageView
        android:layout_width="2dp"
        android:layout_height="0dp"
        android:src="@color/cardview_grey"
        app:layout_constraintTop_toBottomOf="@id/calendar_plan_dot_iv"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="@id/calendar_plan_dot_iv"
        app:layout_constraintEnd_toEndOf="@id/calendar_plan_dot_iv"/>
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/calendar_floating_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="12dp"
        app:backgroundTint="@color/white"
        android:src="@drawable/ic_plus"
        app:layout_constraintTop_toBottomOf="@id/calendar_calendar_cv"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

fragment_calendar_date.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <TextView
        android:id="@+id/tv_day_of_week_0"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textStyle="normal"
        android:textColor="@color/error"
        android:text="일"
        android:textSize="12sp"
        android:fontFamily="@font/spoqa_han_sans_neo_regular"
        android:gravity="center_horizontal"
        android:paddingTop="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/tv_day_of_week_1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_day_of_week_1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="normal"
            android:textColor="@color/line_black"
            android:text="월"
            android:textSize="12sp"
            android:fontFamily="@font/spoqa_han_sans_neo_regular"
            android:gravity="center_horizontal"
            android:paddingTop="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv_day_of_week_2"
            app:layout_constraintStart_toEndOf="@id/tv_day_of_week_0"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_day_of_week_2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="normal"
            android:textColor="@color/line_black"
            android:text="@string/tuesday"
            android:textSize="12sp"
            android:fontFamily="@font/spoqa_han_sans_neo_regular"
            android:gravity="center_horizontal"
            android:paddingTop="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv_day_of_week_3"
            app:layout_constraintStart_toEndOf="@id/tv_day_of_week_1"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_day_of_week_3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="normal"
            android:textColor="@color/line_black"
            android:text="@string/wednesday"
            android:textSize="12sp"
            android:fontFamily="@font/spoqa_han_sans_neo_regular"
            android:gravity="center_horizontal"
            android:paddingTop="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv_day_of_week_4"
            app:layout_constraintStart_toEndOf="@id/tv_day_of_week_2"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_day_of_week_4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="normal"
            android:textColor="@color/line_black"
            android:text="@string/thursday"
            android:textSize="12sp"
            android:fontFamily="@font/spoqa_han_sans_neo_regular"
            android:gravity="center_horizontal"
            android:paddingTop="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv_day_of_week_5"
            app:layout_constraintStart_toEndOf="@id/tv_day_of_week_3"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_day_of_week_5"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="normal"
            android:textColor="@color/line_black"
            android:text="@string/friday"
            android:textSize="12sp"
            android:fontFamily="@font/spoqa_han_sans_neo_regular"
            android:gravity="center_horizontal"
            android:paddingTop="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv_day_of_week_6"
            app:layout_constraintStart_toEndOf="@id/tv_day_of_week_4"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_day_of_week_6"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textStyle="normal"
            android:textColor="@color/saturday_blue"
            android:fontFamily="@font/spoqa_han_sans_neo_regular"
            android:text="@string/saturday"
            android:textSize="12sp"
            android:gravity="center_horizontal"
            android:paddingTop="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/tv_day_of_week_5"
            app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

item_calendar_day.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/item_calendar_day_iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginHorizontal="10dp"
        android:layout_marginVertical="10dp"
        android:visibility="invisible"
        android:src="@drawable/ic_light_blue_dot"/>
    <TextView
        android:id="@+id/item_calendar_day_tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="14sp"
        android:fontFamily="@font/spoqa_han_sans_neo_regular"
        android:gravity="center"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

변경 후기

custom이 cosmocalendar에 비해 확실히 더 자유롭지만, 그만큼 사용하는데 직접 작성해야 할 코드가 상대적으로 많긴 합니다. RecyclerView를 기반으로 하고 있기 때문에 어느 정도 recyclerview를 사용해본 분들은 이해하기 쉬울 것이라 생각합니다.

+ 다른 사람들은 알고 있을 것 같지만 라이브러리를 사용하다 보면 생기는 이슈나 궁금점들이 stackoverflow나 구글링을 해도 나오지 않는 경우가 있는데, 해당 라이브러리의 github페이지에서 closed된 issue들을 보며 해결할 수 있었습니다. 앞으로도 비슷한 상황에서 이를 적극적으로 활용해야겠다는 생각이 들었습니다.

 

참고

kizitonwose/CalendarView: A highly customizable calendar library for Android, powered by RecyclerView. (github.com)