*/Android

[안드로이드] 가로 달력 커스텀 (SingleRowCalendar)

sssbin 2023. 3. 8. 19:07

 

https://github.com/miso01/SingleRowCalendar

 

GitHub - miso01/SingleRowCalendar: Android library for horizontal single row calendar. With this library, you aren't attached to

Android library for horizontal single row calendar. With this library, you aren't attached to library built-in UI. You can create really beautiful and customizable UI and use selection features...

github.com

 

완성본부터..!

사용법, 예제는 위의 링크에 잘 나와있다.

 

 

1. 라이브러리 설치

- build.gradle

dependencies {
    ...
    implementation 'com.michalsvec:single-row-calednar:1.0.0'
    ...
}

- settings.gradle

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

 

2. 선택된 날짜, 선택되지 않은 날짜 디자인 만들기

선택o

<!-- item_selected_calendar.xml -->

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="110dp"
    android:layout_height="35dp"
    android:layout_marginStart="4dp"
    android:layout_marginEnd="4dp"
    android:background="@drawable/bg_selected_calendar"
    android:orientation="horizontal"
    android:weightSum="3"
    android:padding="10dp">

    <TextView
        android:id="@+id/tv_date_calendar_item"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_weight="1"
        android:fontFamily="sans-serif-black"
        android:gravity="center"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:text="20" />

    <TextView
        android:id="@+id/tv_month_calendar_item"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_weight="1"
        android:fontFamily="sans-serif-black"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:text="JAN"/>

    <TextView
        android:id="@+id/tv_year_calendar_item"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_weight="1"
        android:fontFamily="sans-serif-black"
        android:textColor="@color/black"
        android:textSize="12sp"
        android:text="2023"/>

</LinearLayout>

선택x

<!-- item_unselected_calendar.xml -->

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="70dp"
    android:layout_height="35dp"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv_date_calendar_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="22"
        android:textColor="#7F7F7F"
        android:textSize="12sp"
        android:fontFamily="sans-serif-black"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

3. 달력을 띄울 메인 화면에 sigleRowCalendar 추가해주기

<com.michalsvec.singlerowcalendar.calendar.SingleRowCalendar
        android:id="@+id/sel_calendar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        app:deselection="false"
        app:longPress="false"
        app:multiSelection="false"
        app:layout_constraintTop_toBottomOf="@id/btn_calendar"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

 

4. CalendarViewManager (메인 코드 안에)

val myCalendarViewManager = object : CalendarViewManager {
    override fun setCalendarViewResourceId(
        position: Int,
        date: Date,
        isSelected: Boolean
    ): Int {
        // return item layout files, which you have created
        return if (isSelected) R.layout.item_selected_calendar
        else R.layout.item_unselected_calendar
    }

    override fun bindDataToCalendarView(
        holder: SingleRowCalendarAdapter.CalendarViewHolder,
        date: Date,
        position: Int,
        isSelected: Boolean
    ) {
        // bind data to calendar item views
        holder.itemView.findViewById<TextView>(R.id.tv_date_calendar_item).text =
            DateUtils.getDayNumber(date)

        if (isSelected) {
            holder.itemView.findViewById<TextView>(R.id.tv_month_calendar_item).text =
                DateUtils.getMonth3LettersName(date)
            holder.itemView.findViewById<TextView>(R.id.tv_year_calendar_item).text =
                DateUtils.getYear(date)
        }

    }
}

 

선택된/선택되지 않은 날짜에 대해 레이아웃을 반환해준다.

요일에 따라 다른 아이템 레이아웃을 적용하고 싶다면 예제 코드를 참고하면 된다.

 

그리고 아이템을 바인딩해줘야 하는데, 예제에 나온대로 따라하면 자꾸 오류가 떠서 삽질을 좀 했다..

예제 코드

자꾸 바인딩이 안 돼서 findViewById로 해결했다.

 

내가 필요한건 YYYY/MM/DD 였다.

getDayNumber -> 일 반환 (숫자)

getMonth3LettersName -> 월(3글자) 반환 (문자)

getYear -> 년도 반환 (숫자)

 

5. CalendarSelectionManager (메인 코드 안에)

val mySelectionManager = object : CalendarSelectionManager {
    override fun canBeItemSelected(position: Int, date: Date): Boolean {
        // return true if item can be selected
        curDate = simpleDateFormat.format(date)
        MypageService(this@MypageFragment).tryGetDailyRecord(curDate, userIdx)

        return true
    }
}

 

아이템이 선택되었을 때 해야 할 일을 작성해준다.

나는 서버로부터 데이터를 받아와야 하기 때문에 통신 코드를 작성해줬다.

 

6. CalendarChangesObserver (메인 코드 안에)

val myCalendarChangesObserver = object : CalendarChangesObserver {
    override fun whenSelectionChanged(isSelected: Boolean, position: Int, date: Date) {
        super.whenSelectionChanged(isSelected, position, date)
    }
}

 

7. SingleRowCalendar 설정해주기 (메인 코드 안에)

val today = GregorianCalendar()
var date: Int = today.get(Calendar.DATE)

binding.selCalendar.apply {
    calendarViewManager = myCalendarViewManager
    calendarChangesObserver = myCalendarChangesObserver
    calendarSelectionManager = mySelectionManager

    setDates(getFutureDatesOfCurrentMonth())
    initialPositionIndex = date - 3
    init()
    select(date - 1) // 오늘 날짜 선택
}

 

이제 캘린더에 위에서 만들었던 객체들을 설정해주고, 날짜를 설정해준다. 

 

setDates(getFutureDatesOfCurrentMonth())

-> 오늘 날짜가 포함된 month의 날짜들을 설정해준다.

 

initialPositionIndex = date - 3

-> 처음 실행했을 때 스크롤을 하지 않았을 때 첫 번째 인덱스가 오늘날짜-2가 되도록 하였다.

(화면에 총 다섯개의 날짜를 띄웠고, 선택된 날짜를 가운데로 보내기 위함)

 

select(date - 1)

-> 처음 실행했을 때 자동으로 오늘 날짜를 선택하도록 하였다.

 

8. 날짜 불러오는 함수 (메인 코드 밖에)

private fun getFutureDatesOfCurrentMonth(): List<Date> {
    currentYear = calendar[Calendar.YEAR]
    currentMonth = calendar[Calendar.MONTH]
    return getDates(mutableListOf())
}

private fun getFutureDatesOfSelectMonth(month: Int, year: Int): List<Date> {
    currentMonth = month-1
    currentYear = year
    return getDates(mutableListOf())
}

private fun getDates(list: MutableList<Date>): List<Date> {
    calendar.set(Calendar.YEAR, currentYear)
    calendar.set(Calendar.MONTH, currentMonth)
    calendar.set(Calendar.DAY_OF_MONTH, 1)
    list.add(calendar.time)
    while (currentMonth == calendar[Calendar.MONTH]) {
        calendar.add(Calendar.DATE, +1)
        if (calendar[Calendar.MONTH] == currentMonth)
            list.add(calendar.time)
    }
    calendar.add(Calendar.DATE, -1)
    return list
}

 

총 세개의 함수를 작성해줬다.

나는 달력 모양의 버튼이 있어 그 버튼을 클릭하면 달력에서 날짜를 선택할 수 있고,

날짜가 선택되면 해당 날짜에 따라 SingleRowCalendar의 날짜를 바꾸도록 하여서

오늘 날짜가 포함된 month 뿐만 아니라 다른 month의 날짜들도 필요했기 때문이다.

그리고 나는 year도 필요해서 예제코드를 조금 바꿔서 사용했다.

 

getFutureDatesOfCurrentMonth

-> 오늘 날짜가 포함된 month의 날짜 리스트 반환

 

getFutureDatesOfSelectMonth

-> 매개변수로 들어온 month와 year의 날짜 리스트 반환

 

getDates

-> 날짜 리스트 반환

 

9. 완성!

완성본 사진 한번 더

이렇게 하면 끝..!

아주 잘 작동된다~