관리 메뉴

나만을 위한 블로그

[Android] Material CalendarView 커스텀 사용법 정리 본문

Android

[Android] Material CalendarView 커스텀 사용법 정리

참깨빵위에참깨빵_ 2022. 1. 23. 23:22
728x90
반응형

앱에서 달력을 보여주는 방법으로는 안드로이드에서 기본제공하는 캘린더뷰를 쓰는 방법이 있다.

그러나 이 캘린더뷰는 단일 날짜를 지정하는 건 가능하지만 예를 들어 11일~24일까지의 연속된 날짜를 표시하는 건 불가능하다.

그래서 이런 기능을 만들기 위해선 방법은 2개다.

 

  1. 커스텀 뷰를 사용한 캘린더뷰 제작
  2. 캘린더뷰 라이브러리 사용

 

이 포스팅은 2번째 방법으로 캘린더뷰를 만드는 방법에 대해 설명한다.

먼저 사용할 라이브러리는 제목에도 써 있듯 Material CalendarView라는 라이브러리다. MIT 라이선스를 적용받으니 참고하자.

 

https://github.com/prolificinteractive/material-calendarview

 

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

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

github.com

 

사용하려면 면저 프로젝트 수준 gradle 파일과 앱 수준 gradle 파일에 의존성들을 복붙하면 된다. 그리고 추가적으로 라이브러리 하나를 앱 수준 gradle에 추가해야 하는데, 결론적으로 앱 수준 gradle에는 2개의 라이브러리가 추가된다.

 

implementation 'com.github.prolificinteractive:material-calendarview:2.0.1'
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'

 

두 번째 라이브러리는 달력 헤더의 시간대를 "2022 01" 형태로 커스텀할 때 등에 필요한 라이브러리다.

이제 XML에 대충 넣어보고 findViewById()해서 어떻게 보이는지 확인해보자.

 

<?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"
    tools:context=".MainActivity">

    <com.prolificinteractive.materialcalendarview.MaterialCalendarView
        android:id="@+id/calendarview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.prolificinteractive.materialcalendarview.MaterialCalendarView;

public class MainActivity extends AppCompatActivity {

    private MaterialCalendarView calendarView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        calendarView = findViewById(R.id.calendarview);
    }
}

 

실행하면 아래 화면이 나온다.

 

아무런 설정도 하지 않은 날 것의 상태기 때문에 월과 요일이 영어로 나오고 일요일이 가장 먼저 나오는 등 어색한 것 투성이다.

일단 범위 설정부터 해보자. 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"
    tools:context=".MainActivity">

    <com.prolificinteractive.materialcalendarview.MaterialCalendarView
        android:id="@+id/calendarview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:mcv_selectionMode="range"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

mvc_selectionMode와 range를 입력하면 이제 범위 설정이 가능해진다.

 

 

 

이쁜 구석이라곤 하나도 없는 투박한 범위 설정이다. 이제 거슬리는 영어를 한글로 바꿔보고 범위 설정 이미지를 이쁘게 다듬어보자.

 

먼저 strings에 아래의 string-array들을 넣어준다. 달력에서 영어가 아닌 한글로 보이게 할 때 필요하다.

 

<string-array name="custom_weekdays">
    <item>일</item>
    <item>월</item>
    <item>화</item>
    <item>수</item>
    <item>목</item>
    <item>금</item>
    <item>토</item>
</string-array>

<string-array name="custom_months">
    <item>1월</item>
    <item>2월</item>
    <item>3월</item>
    <item>4월</item>
    <item>5월</item>
    <item>6월</item>
    <item>7월</item>
    <item>8월</item>
    <item>9월</item>
    <item>10월</item>
    <item>11월</item>
    <item>12월</item>
</string-array>

 

그리고 필요한 드로어블과 style들을 미리 만든다. 여기서 정의한 대로 달력에 보여지기 때문에 어떻게 적용되는지 확인하고 각자 필요한 방식대로 수정해 사용하면 될 것이다.

 

먼저 res/values/themes에 아래 태그들을 추가한다.

 

<style name="Widget.CalendarView.Custom" parent="android:Widget.CalendarView">
    <item name="android:focusedMonthDateColor">@android:color/holo_blue_light</item>
    <item name="android:weekNumberColor">@android:color/holo_red_light</item>
    <item name="android:selectedWeekBackgroundColor">@android:color/holo_blue_light</item>
    <item name="android:selectedDateVerticalBar">@android:color/holo_blue_light</item>
    <item name="android:unfocusedMonthDateColor">@android:color/holo_blue_light</item>
    <item name="android:weekDayTextAppearance">@style/TextAppearance.AppCompat.Medium</item>
    <item name="android:dateTextAppearance">@style/TextAppearance.AppCompat.Medium</item>
</style>

<!-- 달력에 사용하는 스타일 -->
<style name="CustomTextAppearance" parent="TextAppearance.AppCompat.Medium">
    <item name="android:textSize">20sp</item>
    <item name="android:textColor">@color/mcv_text_date_light</item>
    <item name="android:textStyle">bold</item>
    <item name="android:includeFontPadding">false</item>
</style>

<!-- 여기서 요일, 달력 안 숫자들의 폰트 설정을 수행할 수 있다 -->
<style name="CalenderViewCustom" parent="Theme.AppCompat">
    <item name="colorAccent">@android:color/holo_red_light</item>
    <item name="colorPrimary">@android:color/holo_blue_light</item>
    <item name="android:textStyle">bold</item>
</style>

<!-- 달력 안의 숫자들에 적용되는 스타일 -->
<style name="CalenderViewDateCustomText" parent="android:TextAppearance.DeviceDefault.Small">
    <item name="android:textColor">@android:color/black</item>
    <item name="android:weekNumberColor">@android:color/holo_red_light</item>
</style>

<!-- 달력 안의 요일들에 적용되는 스타일 -->
<style name="CalenderViewWeekCustomText" parent="android:TextAppearance.DeviceDefault.Small">
    <item name="android:textColor">@color/black</item>
    <item name="android:weekNumberColor">@android:color/holo_red_light</item>
</style>

<!-- 연, 월을 표시하는 헤더에 적용되는 스타일 -->
<style name="CalendarWidgetHeader">
    <item name="android:textSize">18sp</item>
    <item name="android:textColor">@android:color/darker_gray</item>
</style>

 

다음은 일자 선택 시 해당 일자의 background 모양을 변경할 때 사용할 드로어블이다. calendar_selector.xml이란 이름으로 만들었다.

 

<?xml version="1.0" encoding="utf-8"?>
<selector android:exitFadeDuration="@android:integer/config_shortAnimTime"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_checked="true"
        android:drawable="@drawable/transparent_calendar_element" />
    <item
        android:state_pressed="true"
        android:drawable="@drawable/transparent_calendar_element" />
    <item android:drawable="@android:color/transparent" />
</selector>

 

이 안에서 사용된 transparent_calendar_element 내용은 아래와 같다.

 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid
        android:color="@android:color/holo_blue_light"/>
</shape>

 

마지막으로 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"
    tools:context=".MainActivity">

    <com.prolificinteractive.materialcalendarview.MaterialCalendarView
        android:id="@+id/calendarview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:theme="@style/CalenderViewCustom"
        app:mcv_dateTextAppearance="@style/CalenderViewDateCustomText"
        app:mcv_selectionMode="range"
        app:mcv_weekDayTextAppearance="@style/CalenderViewWeekCustomText" />

</androidx.constraintlayout.widget.ConstraintLayout>
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import com.prolificinteractive.materialcalendarview.CalendarDay;
import com.prolificinteractive.materialcalendarview.DayViewDecorator;
import com.prolificinteractive.materialcalendarview.DayViewFacade;
import com.prolificinteractive.materialcalendarview.MaterialCalendarView;
import com.prolificinteractive.materialcalendarview.OnRangeSelectedListener;
import com.prolificinteractive.materialcalendarview.format.ArrayWeekDayFormatter;
import com.prolificinteractive.materialcalendarview.format.MonthArrayTitleFormatter;
import com.prolificinteractive.materialcalendarview.format.TitleFormatter;

import org.threeten.bp.DayOfWeek;
import org.threeten.bp.LocalDate;

import java.util.Calendar;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();
    private MaterialCalendarView calendarView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        calendarView = findViewById(R.id.calendarview);

        // 첫 시작 요일이 월요일이 되도록 설정
        calendarView.state()
                .edit()
                .setFirstDayOfWeek(DayOfWeek.of(Calendar.MONDAY))
                .commit();

        // 월, 요일을 한글로 보이게 설정 (MonthArrayTitleFormatter의 작동을 확인하려면 밑의 setTitleFormatter()를 지운다)
        calendarView.setTitleFormatter(new MonthArrayTitleFormatter(getResources().getTextArray(R.array.custom_months)));
        calendarView.setWeekDayFormatter(new ArrayWeekDayFormatter(getResources().getTextArray(R.array.custom_weekdays)));

        // 좌우 화살표 사이 연, 월의 폰트 스타일 설정
        calendarView.setHeaderTextAppearance(R.style.CalendarWidgetHeader);

        // 요일 선택 시 내가 정의한 드로어블이 적용되도록 함
        calendarView.setOnRangeSelectedListener(new OnRangeSelectedListener() {
            @Override
            public void onRangeSelected(@NonNull MaterialCalendarView widget, @NonNull List<CalendarDay> dates) {
                // 아래 로그를 통해 시작일, 종료일이 어떻게 찍히는지 확인하고 본인이 필요한 방식에 따라 바꿔 사용한다
                // UTC 시간을 구하려는 경우 이 라이브러리에서 제공하지 않으니 별도의 로직을 짜서 만들어내 써야 한다
                String startDay = dates.get(0).getDate().toString();
                String endDay = dates.get(dates.size() - 1).getDate().toString();
                Log.e(TAG, "시작일 : " + startDay + ", 종료일 : " + endDay);
            }
        });

        // 일자 선택 시 내가 정의한 드로어블이 적용되도록 한다
        calendarView.addDecorators(new DayDecorator(this));

        // 좌우 화살표 가운데의 연/월이 보이는 방식 커스텀
        calendarView.setTitleFormatter(new TitleFormatter() {
            @Override
            public CharSequence format(CalendarDay day) {
                // CalendarDay라는 클래스는 LocalDate 클래스를 기반으로 만들어진 클래스다
                // 때문에 MaterialCalendarView에서 연/월 보여주기를 커스텀하려면 CalendarDay 객체의 getDate()로 연/월을 구한 다음 LocalDate 객체에 넣어서
                // LocalDate로 변환하는 처리가 필요하다
                LocalDate inputText = day.getDate();
                String[] calendarHeaderElements = inputText.toString().split("-");
                StringBuilder calendarHeaderBuilder = new StringBuilder();
                calendarHeaderBuilder.append(calendarHeaderElements[0])
                        .append(" ")
                        .append(calendarHeaderElements[1]);
                return calendarHeaderBuilder.toString();
            }
        });
    }

    /* 선택된 요일의 background를 설정하는 Decorator 클래스 */
    private static class DayDecorator implements DayViewDecorator {

        private final Drawable drawable;

        public DayDecorator(Context context) {
            drawable = ContextCompat.getDrawable(context, R.drawable.calendar_selector);
        }

        // true를 리턴 시 모든 요일에 내가 설정한 드로어블이 적용된다
        @Override
        public boolean shouldDecorate(CalendarDay day) {
            return true;
        }

        // 일자 선택 시 내가 정의한 드로어블이 적용되도록 한다
        @Override
        public void decorate(DayViewFacade view) {
            view.setSelectionDrawable(drawable);
//            view.addSpan(new StyleSpan(Typeface.BOLD));   // 달력 안의 모든 숫자들이 볼드 처리됨
        }
    }

}

 

이제 앱을 빌드하면 아래와 같이 조금 볼만하게 작동한다.

 

 

여기서 아직 커스텀할 여지는 많다. 토일요일과 공휴일은 색깔을 빨간색과 파란색으로 바꾼다던가, 선택한 날의 글자를 볼드처리해서 보여준다던가 등이다.

그리고 로그를 확인해보면 시작일과 종료일, 달력 헤더의 "2022 01" 부분이 바뀌는 걸 로그로 출력되도록 해놔서 로그캣을 보면 아래처럼 보인다.

 

 

이제 이걸 바탕으로 좀 더 멋진 커스텀 달력을 만들어보자.

반응형
Comments