관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] Android Material Calendar View 사용법 정리 본문

안드로이드/코틀린 & 아키텍처 & Recent

[안드로이드] Android Material Calendar View 사용법 정리

막무가내막내 2020. 9. 9. 12:06
728x90

 

 

[2021-04-14 업데이트]

 

달력 라이브러리와 사용법에 대한 정리입니다 ㅎㅎ

지금까지 기본 캘린더뷰 부터 시작해서 총 3가지 정도의 캘린더 뷰를 사용해봤는데요.

오늘 다룰 것은 다음 캘린더뷰 라이브러리 입니다.

github.com/prolificinteractive/material-calendarview

 

prolificinteractive/material-calendarview

A Material design back port of Android's CalendarView - prolificinteractive/material-calendarview

github.com

 

자세한 방법은 해당 깃허브에 들어가도 나오므로 저는 간략하게 사용한 것을 기록하려고 합니다.

 

 

 

1. dependency 추가

implementation 'com.github.prolificinteractive:material-calendarview:2.0.1'

 

 

2. xml

선택 모드, 주단위 월단위. 색상 등을 변경할 수 있습니다. (app:mcv_)

<com.prolificinteractive.materialcalendarview.MaterialCalendarView
                    android:id="@+id/cv_calendar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:visibility="gone"
                    app:layout_constraintTop_toBottomOf="@id/view_date"
                    app:mcv_selectionColor="#f79256"
                    app:mcv_selectionMode="range" />

 

 

 

3. 코드 

저같은 경우 range(범위) 선택 모드일 경우를 구현 했습니다. 주의하실 점은 범위 모드일 경우 날짜를 두 개 클릭해서 범위가 되지 않으면 setOnRangeSelectedListener 로 콜백이 안오므로 하나만 클릭 했을 경우를 대비해 setOnDateChangedListener 를 추가로 사용했습니다.

두 리스너를 간단히 설명하면

 

setOnRangeSelectedListener-> 처음 날짜와 두번 째 날짜를 선택한 경우(범위지정 한 경우) 콜백이 오고 해당 범위의 날짜들이 리스트로(dates) 온다.

widget, dates ->

 

 

setOnDateChangedListener-> 처음 날짜를 선택한 경우 해당 날짜(date) 콜백이온다. Range모드 일떄 두번째 날짜 선택시에는 콜백이 안오고 setOnRangeSelectedListener 에만 콜백이 온다. 범위 지정이 되어있는 경우 해당 범위지정된 날짜를 다시 한번 클릭하면 범위지정이 취소되는데 그때도 여기로 setOnDateChangedListener 콜백이 온다.

 widget, date, selected -> 

첫번째 날짜 선택했을때 콜백과 다른점은 selected 가 첫 날짜 선택시에는 true 범위지정된 날짜를 다시 한번 누른 경우는 false 로 넘어 온다는 점이다.

 

 

그리고 setOnRangeSelectedListener 의 경우 시작날짜와 선택날짜를 선택한 경우 콜백이 올텐데 이 범위에 있는 날짜들을 모두 포함한 CalendarDay 라는 타입의 리스트로 오게 됩니다. 

저 같은 경우 확장함수를 하나 만들어 해당 2020-1-2 이런 날짜를 타임스탬프로 변환해주는 함수를 만들어 Long 형으로 변환하는 작업을 추가로 했습니다. (밑에 첨부해놨습니다.)

binding.cvCalendar.setOnRangeSelectedListener { widget, dates ->
            if (!viewModel.checkDatesAvailable(dates)) {
                showToast(getString(R.string.date_can_not_selected_msg))
                binding.cvCalendar.clearSelection()
                viewModel.initDateRange()
                return@setOnRangeSelectedListener
            }
            viewModel.setStartDateTimestamp(dates[0])
            if (dates.size == 0) {
                viewModel.setEndDateTimestamp(dates[dates.size])
            } else if (dates.size != 1) {
                viewModel.setEndDateTimestamp(dates[dates.size - 1])
            }
            viewModel.setDateRange()
        }
        binding.cvCalendar.setOnDateChangedListener { widget, date, selected ->
            val calList = ArrayList<CalendarDay>()
            calList.add(date)
            when {
                !selected -> {
                    viewModel.initDateRange()
                    viewModel.isDateSelected = false
                    viewModel.setAllSelected()
                }
                !viewModel.checkDatesAvailable(calList) -> {
                    showToast(getString(R.string.date_can_not_selected_msg))
                    binding.cvCalendar.clearSelection()
                    viewModel.initDateRange()
                }
                else -> {
                    viewModel.setStartDateTimestamp(date)
                    viewModel.setEndDateTimestamp(date)
                    viewModel.isDateSelected = selected
                    viewModel.setAllSelected()
                    viewModel.setSingleDateRange()
                }
            }
        }
// 2020, 1, 20 -> timestamp
fun convertDateToTimestamp(_year: Int, _month: Int, _day: Int): Long {
    val month = _month.toString().convertSingleToDoubleDigit().toInt()
    val day = _day.toString().convertSingleToDoubleDigit().toInt()
    val date = "$_year-$month-$day"
    return date.convertDateToTimestamp()
}


// 한자리 숫자면 두자리로 변환
fun String.convertSingleToDoubleDigit(): String = if (this.length < 2) "0$this" else this

// 날짜만 타임스탬프 변환 2020-01-01 - timestamp
fun String.convertDateToTimestamp(): Long =
    SimpleDateFormat("yyyy-MM-dd", Locale.KOREA).parse(this).time

 

 

 

3. 최소날짜 최대 날짜

최소날짜와 최대날짜도 지정할 수 있습니다. 저는 오늘 이하 날짜를 막기 위해 다음과 같이 지정하였습니다.

 binding.cvCalendar.state().edit()
            .setMinimumDate(
                CalendarDay.from(
                    getCurrentYear(),
                    getCurrentMonth(),
                    getCurrentDay() + 1
                )
            )
            .setCalendarDisplayMode(CalendarMode.WEEKS)
            .commit()
// 현재 Year
fun getCurrentYear(): Int = Calendar.getInstance().get(Calendar.YEAR)

// 현재 Month
fun getCurrentMonth(): Int = Calendar.getInstance().get(Calendar.MONTH) + 1

// 현재 Day
fun getCurrentDay(): Int = Calendar.getInstance().get(Calendar.DAY_OF_MONTH)

 

 

 

 

4. 3번의 경우 특정 하나의 범위만 막을 수 있습니다. 만약 여러개의 범위 또는 날짜를 막고 싶다면 다음과 같이 Decorator 클래스를 하나만들어 커스텀 할 수 있습니다. 선택을 막을 수도 있고 아이콘이나 배경색을 바꿀 수도 있습니다.

다음과 같이 샘플을 하나 짜봤씁니다.

DayViewDecorator 를 상속받는 커스텀 클래스 생성 (저의 경우 해당 날짜의 선택을 막게하고 X표시 이미지로 했습니다.)

주의할 점은 해당 setDaysDisabled 는 해당 날짜의 선택만 막게하고 범위지정은 못 막습니다.

예를들면 14 ~ 15일이 예약이 차있고 setDaysDisabled 로 설정한 경우 해당 날짜 선택은 막을 수 있지만 13일을 누르고 16일 누르는 range(범위) 선택을 하는 경우는 범위에 포함되는 것을 못 막습니다. 이 점은 따로 처리를 해주어야 했습니다.

package com.mtjin.nomoneytrip.views.reservation_phase_first

import android.R
import android.app.Activity
import android.graphics.drawable.Drawable
import com.mtjin.nomoneytrip.utils.getMyDrawable
import com.prolificinteractive.materialcalendarview.CalendarDay
import com.prolificinteractive.materialcalendarview.DayViewDecorator
import com.prolificinteractive.materialcalendarview.DayViewFacade

class CurrentDayDecorator(context: Activity?, currentDay: CalendarDay) : DayViewDecorator {
    private val drawable: Drawable = context?.getMyDrawable(R.drawable.ic_menu_close_clear_cancel)!!
    private var myDay = currentDay
    override fun shouldDecorate(day: CalendarDay): Boolean {
        return day == myDay
    }

    override fun decorate(view: DayViewFacade) {
        //view.setSelectionDrawable(drawable!!)
        view.setBackgroundDrawable(drawable)
        view.setDaysDisabled(true)
    }

    init {
        // You can set background for Decorator via drawable here
    }
}

 

프래그먼트와 액티비티에 추가

val calList = ArrayList<CalendarDay>()
        calList.add(CalendarDay.from(2020, 9, 20))
        calList.add(CalendarDay.from(2020, 9, 21))
        calList.add(CalendarDay.from(2020, 9, 22))
        calList.add(CalendarDay.from(2020, 9, 23))
        for (calDay in calList){
            binding.cvCalendar.addDecorators(CurrentDayDecorator(requireActivity(),calDay))
        }

 

그럼 해당 날짜들이 선택을 못하게됩니다. 

 

 

 

 

[결과 사진] 

실제 프로젝트의 이미지입니다.

 

 

 

 

 

 

 

 

이 상

prolificinteractive/material-calendarview

에 대해 알아봤습니다.

 

댓글과 공감은 큰 힘이 됩니다. 감사합니다. !!

 

 

 

 

출처 :

github.com/mtjin/NoMoneyTrip/blob/master/NoMoneyTrip/app/src/main/java/com/mtjin/nomoneytrip/views/reservation/ReservationFragment.kt

 

mtjin/NoMoneyTrip

SKT 2020 스마트 관광앱 공모전 '무전여행' 앱. Contribute to mtjin/NoMoneyTrip development by creating an account on GitHub.

github.com

 

728x90
Comments