[Kotlin/Android] MaterialCalendarView 커스텀하기 + 라이브러리 설정 (달력 다이얼로그 만들기)
목표
내가 현재 만드고자 하는 달력 다이얼로그는 아래와 같은 형식이다.
포인트는
(1) 달력 다이얼로그
(2) 시간입력 창 추가
(3) 닫기 버튼
이 세 가지 기능이 필수로 들어가야 하며, 디자인도 이쁘게 커스텀할 수 있다면 좋을 것 같다 !
▼참고한 문서
material-calendarview/README.md at master · prolificinteractive/material-calendarview · GitHub
material-calendarview/README.md at master · prolificinteractive/material-calendarview
A Material design back port of Android's CalendarView - prolificinteractive/material-calendarview
github.com
▼ 만들어 보자
(1) 라이브러리 설정
먼저 앱 수준의 build.gradle.kts 파일에 아래 코드를 추가해준다.
dependencies { // 캘린더뷰 커스텀을 위해 implementation("com.github.prolificinteractive:material-calendarview:1.4.3") }
내가 참고한 문서를 보면 아래와 같이 다양한 버전이 있는 것 같다만.
그건 난 나중에 보도록 하고 일단은 돌아가는 1.4.3 버전을 사용하기로 한다.
여러분은 2.0.1 인 최신버전부터 사용하시는 걸 추천드립니다
이거 전에도 라이브러리 종속 중복 문제 떄문에 글을 하나 썼었는데.
android.support와 androidx 라이브러리 간의 충돌로 인해 발생하는 중복 클래스 문제가 계속 발생해서 애 좀 먹었다. android.support 라이브러리는 이전 Android 지원 라이브러리이며, androidx는 이를 대체하는 최신 라이브러리이다.
현재 프로젝트에서 androidx와 android.support를 동시에 사용하려고 시도하다 보니 충돌이 발생했던 것.
그치만 아래 쓸 코드하나로 해결 가능
settings.gradle.kts 파일에dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { . . . maven { setUrl("https://jitpack.io") } } }저 https://jitpack.io 코드 추가
근데 만약 이렇게해도 안되신다면....
gradle.properties 파일에 아래 코드를 추가해보십쇼
지원 라이브러리 (android.support.*)를 자동으로 AndroidX로 변환해주는 도구입니다.
android.useAndroidX=true android.enableJetifier=true
라이브러리 설정은 여기까지 이고 싱크하고 한 번 빌드나 실행 해보십숑
되신다면 이어서 따라오시구요~
(2) xml 설정 [ dialog_material_calendar.xml ]
저는 다이얼로그로 따로 달력창을 띄워줄 것이기 때문에 class를 따로 만들어서 나중에 Fragment화면에서 호출을 해줄 겁니다. 그 class의 binding으로 있을 xml 파일을 만들어봅시다
코드는 아래와 같습니다
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical" tools:context=".util.MaterialCalendarDialog"> <com.prolificinteractive.materialcalendarview.MaterialCalendarView android:id="@+id/calendarView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:theme="@style/CalenderViewCustom" app:mcv_calendarMode="month" app:mcv_dateTextAppearance="@style/CalenderViewDateCustomText" app:mcv_firstDayOfWeek="sunday" app:mcv_headerTextAppearance="@style/CalendarWidgetHeader" app:mcv_selectionColor="@color/text_green" app:mcv_selectionMode="range" app:mcv_showOtherDates="all" app:mcv_weekDayTextAppearance="@style/CalenderViewWeekCustomText" tools:mcv_headerTextAppearance="@style/CalendarWidgetHeader" /> <View android:id="@+id/divider14" android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/gray" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:orientation="horizontal"> <TextView android:id="@+id/textViewStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="시작" /> <TextView android:id="@+id/textViewstartDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:background="@drawable/bg_textview_calendarview_gray" android:hint="YYYY-MM-DD" android:inputType="date" android:textSize="16sp" /> <TextView android:id="@+id/textViewstartTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2" android:background="@drawable/bg_textview_calendarview_gray" android:hint="H:M" android:inputType="date" android:textSize="16sp"/> </LinearLayout> <View android:id="@+id/divider15" android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/gray" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:orientation="horizontal"> <TextView android:id="@+id/textViewEnd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="종료" /> <TextView android:id="@+id/textViewendDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:background="@drawable/bg_textview_calendarview_gray" android:hint="YYYY-MM-DD" android:inputType="date" android:textSize="16sp"/> <TextView android:id="@+id/textViewendTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2" android:background="@drawable/bg_textview_calendarview_gray" android:hint="H:M" android:inputType="date" android:textSize="16sp"/> </LinearLayout> <View android:id="@+id/divider16" android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/gray" /> <android.widget.Button android:id="@+id/buttonMaterialCalendarClose" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="10dp" android:background="@drawable/bg_button_rounded_border_gray" android:text="닫기" android:textColor="@color/white" /> </LinearLayout> </LinearLayout>
여기서 중요한건
이 부분 ! 잘 넣어주세요.
그리하면 팔레트 화면에 아래와 같이 구현될 것 이다.
(3) class설정 [ MaterialCalendarDialog.kt ]
메인코드 작성시 고려해야할 점은
▼ 주 기능 mcv_selectionMode는 range로 하였지만
① 날짜를 하나만 선택한 경우
② 날짜를 두 개 선택한 경우 (시작일, 종료일)
③ 캘린더뷰 안에 타임피커 넣기 (시작일의 시작시간, 종료일의 종료시간)
▼ 부 기능
- 날짜 형식
- 달 별로 색 변경
등 등
package com.psg.smartflowoperation.util import android.app.TimePickerDialog import android.graphics.Color import android.icu.util.Calendar import android.os.Bundle import android.text.style.ForegroundColorSpan import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.prolificinteractive.materialcalendarview.CalendarDay import com.prolificinteractive.materialcalendarview.DayViewDecorator import com.prolificinteractive.materialcalendarview.DayViewFacade import com.psg.smartflowoperation.R import com.psg.smartflowoperation.databinding.DialogMaterialCalendarBinding class MaterialCalendarDialog : DialogFragment() { lateinit var _binding : DialogMaterialCalendarBinding // 시작일과 종료일을 문자열로 지정 private var selectedStartDaySchedule: String? = null private var selectedEndDaySchedule: String? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // Inflate the layout for this fragment _binding = DialogMaterialCalendarBinding.inflate(inflater) return _binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) InitView() setEvent() } private fun InitView(){ // 년/월이 보이는 방식 지정 _binding.calendarView.setTitleFormatter { day -> // CalendarDay의 년도와 월을 개별적으로 가져옴 val year = day.year val month = day.month // 원하는 포맷으로 문자열 생성 "${year}년 ${month}월" } // 데코레이터를 직접 구현하여 다른 달의 날짜 색상을 변경 _binding.calendarView.addDecorator(object : DayViewDecorator { override fun shouldDecorate(day: CalendarDay): Boolean { val today = CalendarDay.today() // 현재 달이 아닌 이전/다음 달의 날짜인지 확인 return day.month != today.month || day.year != today.year } override fun decorate(view: DayViewFacade) { // 다른 달의 날짜 텍스트 색상을 회색으로 변경 view.addSpan(ForegroundColorSpan(ContextCompat.getColor(requireContext(),R.color.gray))) } }) } private fun setEvent(){ // 날짜가 시작일, 종료일 범위로 설정되었을 때 리스너 _binding.calendarView.setOnRangeSelectedListener { widget, dates -> selectedStartDaySchedule = formatCalendarDay(dates.first()) selectedEndDaySchedule = formatCalendarDay(dates.last()) // textViewStartDate와 textViewEndDate에 값 표시 _binding.textViewstartDate.text = selectedStartDaySchedule _binding.textViewendDate.text = selectedEndDaySchedule } // 날짜가 단일 선택되었을 때 리스너 _binding.calendarView.setOnDateChangedListener { widget, date, selected -> if (selected) { // 단일 날짜 선택시, 둘 다 시작일로 업데이트 selectedStartDaySchedule = formatCalendarDay(date) selectedEndDaySchedule = formatCalendarDay(date) // textViewStartDate와 textViewEndDate에 값 표시 _binding.textViewstartDate.text = selectedStartDaySchedule _binding.textViewendDate.text = selectedStartDaySchedule } } // startTime textView 클릭시 타임피커 다이얼로그 호출 리스너 _binding.textViewstartTime.setOnClickListener { val timePicker = TimePickerDialog(requireContext(),TimePickerDialog.THEME_HOLO_LIGHT, { _, hour, minute -> // 시간 설정 처리 val time = String.format("%02d:%02d",hour, minute) _binding.textViewstartTime.text = time }, Calendar.getInstance().get(Calendar.HOUR), Calendar.getInstance().get(Calendar.MINUTE), true ) timePicker.show() } // endTime textView 클릭시 타임피커 다이얼로그 호출 리스너 _binding.textViewendTime.setOnClickListener { val timePicker = TimePickerDialog(requireContext(),TimePickerDialog.THEME_HOLO_LIGHT, { _, hour, minute -> // 시간 설정 처리 val time = String.format("%02d:%02d",hour, minute) _binding.textViewendTime.text = time }, Calendar.getInstance().get(Calendar.HOUR), Calendar.getInstance().get(Calendar.MINUTE), true ) timePicker.show() } // buttonMaterialCalendarClose 버튼 클릭시 다이얼로그 창 닫힘 _binding.buttonMaterialCalendarClose.setOnClickListener { // 다이얼로그 닫기 dismiss() } // 캘린더에 보여지는 Month가 변경된 경우 _binding.calendarView.setOnMonthChangedListener { widget, date -> // 기존에 설정되었던 Decarators 초기화 _binding.calendarView.removeDecorators() _binding.calendarView.invalidateDecorators() } } // CalendarDay를 "yyyy-MM-dd" 형식으로 포맷하는 함수 private fun formatCalendarDay(day: CalendarDay): String { return String.format("%04d-%02d-%02d", day.year, day.month, day.day) } }
(4) 결과 실행화면
만드는 데 생각보다 오래 걸렸습니다...
라이브러리 세팅하는데서 애 좀 먹었고요 ㅠ....ㅠ
어쩃든 끝 - !
월요병 도지는 오늘..
모두들 화이팅 -! 맛있는거 먹고 힘내자구요-!





