Android

[Kotlin/Android] MaterialCalendarView 커스텀하기 + 라이브러리 설정 (달력 다이얼로그 만들기)

연나연 2024. 9. 2. 10:02

목표

내가 현재 만드고자 하는 달력 다이얼로그는 아래와 같은 형식이다.
 포인트는
(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) 결과 실행화면

만드는 데 생각보다 오래 걸렸습니다...
라이브러리 세팅하는데서 애 좀 먹었고요 ㅠ....ㅠ
어쩃든 끝 - !

 

월요병 도지는 오늘.. 
모두들 화이팅 -! 맛있는거 먹고 힘내자구요-!