- 한 주씩만 나오는 달력임
- 스와이프 기능 없음
팀 프로젝트에서 주간 달력이 있었다. 깃허브 등 라이브러리를 사용해봤지만 여러 문제점이 있었다.
- 리사이클러뷰 : 오늘 날짜(ex. 21일)이 먼저 출력되지 않고 항상 1일부터 보임
- 뷰페이저 : 예전날짜/다음날짜 스크롤에 한계가 있음 (무한 불러오기 안됨)
- 이전/다음 날짜 무한으로 불러오게 하면 기기가 감당을 못하고 다운됨
뷰페이저 사용했을 때가 가장 근접하게 성공했지만... 불러올 수 있는 날짜에 한계가 있어서.. (ex. 최대 n년 전, n년 후까지로 설정을 미리 해야 함) 위의 사진처럼 화살표 버튼을 추가하고 버튼 누를 때마다 저번주/다음주 날짜 불러오게 하였다.
아래 링크가 뷰페이저로 성공했던 라이브러리다.
주간 달력뿐만 아니라 펼쳤다가 접는 달력 등 매우 다양한 종류의 달력을 주기 때문에 참고하고 싶으면 이대로 해도 된다.
https://github.com/kizitonwose/Calendar?tab=readme-ov-file
지금부터는 내가 사용한 방식이다.
설명 시작
1. 간단한 원리 설명
- LocalDate를 사용하여 이번주 날짜를 구한다.
- 저번주/다음주 이동 버튼 클릭 시 이번주를 기준으로 저번주/다음주를 계산하고 덮어씌우고 textView에도 적용한다.
- 다시 저번주/다음주 이동 버튼 클릭 시 덮어씌어진 주를 기준으로 저번주/다음주를 계산하고 다시 덮어씌운다.
2. xml에 UI 배치
년.월, 양쪽 화살표, 요일 등 모두 imageView, textView로 배치하였다
2-1. 오늘 날짜 표시 (동그란 원)
이건 따로 적용할 모양을 xml로 만들었다
calendar_circle.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/mint"/>
<size android:width="35dp" android:height="35dp"/>
</shape>
이걸 실제 사용할 fragment.xml에 넣는다.
fragment.xml
높이는 아래 있는 날짜 높이와 맞추도록 한다.
id는 todayCircle로 했다.
<ImageView
android:id="@+id/todayCircle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/calendar_circle" />
2-2. 저번주/다음주 이동 버튼
이미지를 적용하였다.
저번주는 beforeBtn, 다음주는 afterBtn로 했다.
fragment.xml
<ImageView
android:id="@+id/beforeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_navi_left" />
<ImageView
android:id="@+id/afterBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_navi_right" />
2-3. 요일
월-화-수-목-금-토-일 순으로 id이름을 postDay1~postDay7로 했다.
fragment.xml
<TextView
android:id="@+id/postDay1"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginStart="21dp"
android:layout_marginTop="158dp"
android:gravity="center"
android:text="월"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDay2"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginTop="158dp"
android:gravity="center"
android:text="화"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/postDay3"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDay1"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDay3"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginTop="158dp"
android:gravity="center"
android:text="수"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/postDay4"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDay2"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDay4"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginTop="158dp"
android:gravity="center"
android:text="목"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/postDay5"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDay3"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDay5"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginTop="158dp"
android:gravity="center"
android:text="금"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/postDay6"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDay4"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDay6"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginTop="158dp"
android:gravity="center"
android:text="토"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/postDay7"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDay5"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDay7"
android:layout_width="11dp"
android:layout_height="16dp"
android:layout_marginTop="158dp"
android:layout_marginEnd="21dp"
android:gravity="center"
android:text="일"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
2-4. 날짜
월-화-수-목-금-토-일 순으로 id이름을 postDate1~postDate7로 했다.
fragment.xml
<TextView
android:id="@+id/postDate1"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginStart="16dp"
android:layout_marginTop="186dp"
android:gravity="center"
android:text="1"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDate2"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginTop="186dp"
android:gravity="center"
android:text="2"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/postDate3"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDate1"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDate4"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginTop="186dp"
android:gravity="center"
android:text="3"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/postDate5"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDate3"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDate3"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginTop="186dp"
android:gravity="center"
android:text="4"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/postDate4"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDate2"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDate5"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginTop="186dp"
android:gravity="center"
android:text="5"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/postDate6"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDate4"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDate6"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginTop="186dp"
android:gravity="center"
android:text="6"
android:textColor="@color/blue"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/postDate7"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/postDate5"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postDate7"
android:layout_width="21dp"
android:layout_height="20dp"
android:layout_marginTop="186dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:text="7"
android:textColor="@color/red"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
2-5. 년/월 표시
해당 주가 속한 년/월의 id를 selectDateTv로 했다.
fragment.xml
<TextView
android:id="@+id/selectDateTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="14sp"/>
3. fragment에 적용
3-1. 날짜 포맷 함수 세팅
원하는 날짜 표현 방식으로 설정한다.
나는 일자만 원하니 아래 형식으로 했다.
private fun formatDate(date: LocalDate): String {
val formatter = DateTimeFormatter.ofPattern("dd", Locale.getDefault())
return date.format(formatter)
}
3-2. 오늘 날짜 불러오기
LocalDate.now()로 오늘 날짜를 부른다.
변수는 currentStartOfWeek로 했다.
class MyFragment : Fragment() {
private var currentStartOfWeek: LocalDate = LocalDate.now()
3-3. 이번주 날짜 세팅하기
오늘이 포함된 이번주 날짜를 적용한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = MyBinding.inflate(inflater, container, false)
//// 달력
setWeek(currentStartOfWeek)
}
private fun setWeek(startOfWeek: LocalDate) {
val nearestMonday = startOfWeek.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
val yearMonth = YearMonth.from(nearestMonday)
binding.selectDateTv.text = "${yearMonth.year}년 ${yearMonth.monthValue}월" // 해당 주 년월 적용
for (i in 1..7) {
val currentDateForDay = nearestMonday.plusDays(i.toLong() - 1)
val dateTextView = when (i) {
1 -> binding.postDate1
2 -> binding.postDate2
3 -> binding.postDate3
4 -> binding.postDate4
5 -> binding.postDate5
6 -> binding.postDate6
7 -> binding.postDate7
else -> null
}
val dayTextView = when (i) {
1 -> binding.postDay1
2 -> binding.postDay2
3 -> binding.postDay3
4 -> binding.postDay4
5 -> binding.postDay5
6 -> binding.postDay6
7 -> binding.postDay7
else -> null
}
dateTextView?.text = formatDate(currentDateForDay)
// 오늘 날짜에 동그라미 표시
val today = LocalDate.now()
val isTodayInWeek = startOfWeek <= today && today <= startOfWeek.plusDays(6)
if (isTodayInWeek) {
if (today == currentDateForDay) { // 오늘 날짜일 경우
binding.todayCircle.visibility = View.VISIBLE // 동그라미 보이게
dateTextView?.viewTreeObserver?.addOnPreDrawListener(object :
ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean { // 동그라미 x축 오늘 날짜에 맞추기
dateTextView.viewTreeObserver.removeOnPreDrawListener(this)
val dateTextViewX = dateTextView.x
val dateTextViewWidth = dateTextView.width.toFloat()
val circleWidth = binding.todayCircle.width.toFloat()
binding.todayCircle.x =
dateTextViewX + (dateTextViewWidth - circleWidth) / 2
return true
}
})
// 색상 적용
dateTextView?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
dayTextView?.setTextColor(Color.parseColor("#1D1D1D")))
}
} else { // 오늘이 아닌 경우
// 색상 적용
dateTextView?.setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
dayTextView?.setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
binding.todayCircle.visibility = View.GONE
// 주말일 경우 색상 다르게
if (dateTextView?.id == binding.postDate6.id){
dateTextView?.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue))
}
if (dateTextView?.id == binding.postDate7.id){
dateTextView?.setTextColor(ContextCompat.getColor(requireContext(), R.color.red))
}
}
}
}
3-4. 저번주/다음주 이동 버튼
저번주/다음주 버튼 클릭 시 그 주가 보이게 하는 코드를 추가한다.
이번주 날짜 세팅했던 setWeek(currentStartOfWeek)아래에 추가한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = MyBinding.inflate(inflater, container, false)
//// 달력
setWeek(currentStartOfWeek)
///// -------------- 여기까지가 방금 위에서 추가했던 코드
///// 여기서부터 다시 복붙
// 저번주
binding.beforeBtn.setOnClickListener {
currentStartOfWeek = currentStartOfWeek.minusWeeks(1)
val yearMonth = YearMonth.from(currentStartOfWeek)
binding.selectDateTv.text = "${yearMonth.year}년 ${yearMonth.monthValue}월"
setWeek(currentStartOfWeek)
}
// 다음주
binding.afterBtn.setOnClickListener {
currentStartOfWeek = currentStartOfWeek.plusWeeks(1)
val yearMonth = YearMonth.from(currentStartOfWeek)
binding.selectDateTv.text = "${yearMonth.year}년 ${yearMonth.monthValue}월"
setWeek(currentStartOfWeek)
}
}
3-5. 날짜 클릭 시 이벤트
날짜 클릭 후 이벤트를 발생시키고 싶다면 setWeek함수에 setOnClickListener를 추가하면 된다.
private fun setWeek(startOfWeek: LocalDate) {
///// (생략)
for (i in 1..7) {
///// (생략)
////// 여기서부터 추가
// 날짜 선택 시
dateTextView?.setOnClickListener {
val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val selectedDateText = currentDateForDay.format(dateFormat) // 선택한 날짜
///// 여기서 원하는 처리 코드 추가
}
}
}
'공부 > 안드로이드' 카테고리의 다른 글
[Android/Kotlin] 카카오 로그인/회원가입 (0) | 2024.02.21 |
---|---|
[Android/Kotlin] Custom Dialog (팝업) (0) | 2024.02.21 |
[Android/Kotlin] fragment에서 fragment로 이동 (0) | 2024.02.21 |
[Android/Kotlin] 안드로이드 spinner(스피너) 배경색 적용 (0) | 2024.02.21 |
[Android/Kotlin] 안드로이드 스튜디오 알림 안뜸 해결 (0) | 2023.11.05 |