[Android] 간단한 커스텀 캘린더

WonDDak·2025년 3월 5일

Android

목록 보기
9/10

kotlinx-datetime라이브러리 필요

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime

@Composable
fun CalendarView(
    start: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
) {
    var year by remember {
        mutableIntStateOf(start.year)
    }
    var month by remember {
        mutableIntStateOf(start.monthNumber)
    }
    Column {
        // 현재 날짜 정보
        Row(
            Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text("${year}${month}월", Modifier.padding(8.dp))
            Row {
                Button(
                    onClick = {
                        if (month == 1) {
                            year -= 1
                            month = 12
                        } else {
                            month -= 1
                        }
                    }
                ) {
                    Text("<")
                }
                Button(
                    onClick = {
                        if (month == 12) {
                            year += 1
                            month = 1
                        } else {
                            month += 1
                        }
                    }
                ) {
                    Text(">")
                }
            }
        }

        // 요일 헤더 추가
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) {
            listOf("일", "월", "화", "수", "목", "금", "토").forEach { day ->
                Text(
                    day,
                    Modifier.weight(1f),
                    textAlign = androidx.compose.ui.text.style.TextAlign.Center
                )
            }
        }

        Spacer(modifier = Modifier.height(8.dp))

        val daysInMonth = getDaysOfMonthWithOffset(year, month)

        // 달력 표시 (7열 고정)
        LazyVerticalGrid(
            columns = GridCells.Fixed(7),
            modifier = Modifier.height(25.dp * 6)
        ) {
            items(daysInMonth) { day ->
                Text(
                    text = day?.dayOfMonth?.toString() ?: "",
                    textAlign = TextAlign.Center,
                    modifier = Modifier.height(25.dp)
                )
            }
        }
    }
}

// 해당 연도와 월의 모든 날짜 리스트 반환 (빈 칸 포함)
fun getDaysOfMonthWithOffset(year: Int, month: Int): List<LocalDate?> {
    val firstDay = LocalDate(year, month, 1)
    val firstDayOfWeek = firstDay.dayOfWeek.ordinal // 0(월) ~ 6(일) → 한국 기준 일요일이 6

    val days = mutableListOf<LocalDate?>()

    // 빈 칸 추가 (일요일부터 시작해야 하므로 shift 조정)
    repeat((firstDayOfWeek + 1) % 7) { days.add(null) }

    // 날짜 추가
    var currentDay = firstDay
    val nextMonth = if (month == 12) 1 else month + 1
    val nextYear = if (month == 12) year + 1 else year
    val firstDayOfNextMonth = LocalDate(nextYear, nextMonth, 1)

    while (currentDay < firstDayOfNextMonth) {
        days.add(currentDay)
        currentDay = currentDay.plus(1, DateTimeUnit.DAY)
    }

    return days
}

@Preview(showBackground = true)
@Composable
fun CalendarViewPreview() {
    CalendarView()
}
profile
안녕하세요. 원딱입니다.

0개의 댓글