Android Studio/Front
[Android Studio][Kotlin] 캘린더 Custom UI
devYeON_
2022. 4. 1. 23:12
😁 기존의 CalendarView가 아닌 CustomCalendar를 사용하고 싶을 때 참고해주세요 😁
Step 1. RecyclerView를 생성해주세요.
중첩 Recyclerview를 사용하여 캘린더를 생성하기 위해 가장 큰 Recyclerview를 생성해줍니다.
<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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/customCalendar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 2. 가장 큰 뼈대인 MonthRecyclerView Item을 생성해줍니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/fragment_calender_dateMonth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:text="March 2021"
android:textColor="@color/black"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
app:layout_constraintBottom_toBottomOf="@+id/fragment_calender_dateMonth"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/fragment_calender_dateMonth">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<TextView
android:id="@+id/textView11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Sun" />
<TextView
android:id="@+id/textView13"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Mon" />
<TextView
android:id="@+id/textView15"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Tue" />
<TextView
android:id="@+id/textView16"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Wed" />
<TextView
android:id="@+id/textView17"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Thu" />
<TextView
android:id="@+id/textView18"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Fri" />
<TextView
android:id="@+id/textView19"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Sat" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fragment_calender_dayRv"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_calendar_day" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
Step 3. 세부적인 날짜가 들어가는 DayRecyclerview Item을 생성합니다.
<?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:id="@+id/fragment_calender_day_item"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/fragment_calender_dayTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:padding="5dp"
android:text="0"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/fragment_calendar_point"
android:layout_width="5dp"
android:layout_height="5dp"
android:layout_marginTop="10dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fragment_calender_dayTv"
app:srcCompat="@drawable/calendar_point" />
</androidx.constraintlayout.widget.ConstraintLayout>
📢 여기서 ImageView 추가적으로 일정이 생성되었을 때, 표시를 해주기 위한 동그라미 포인트입니다!
Step 4. 이제 어댑터를 생성합니다. 먼저 MonthAdapter입니다.
package com.whitebear.customactivity
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.util.*
class MonthAdapter(var context: Context, val date:ArrayList<String>) : RecyclerView.Adapter<MonthAdapter.MonthViewHolder>(){
val center = Int.MAX_VALUE/2
private var calendar = Calendar.getInstance()
inner class MonthViewHolder(val layout: View):RecyclerView.ViewHolder(layout)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MonthViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_month,parent,false)
return MonthViewHolder(view)
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MonthViewHolder, position: Int) {
calendar.time = Date()
calendar.set(Calendar.DAY_OF_MONTH,1)
calendar.add(Calendar.MONTH,position-center)
// Month를 해당 영어로 바꾸기 위한 과정
holder.layout.findViewById<TextView>(R.id.fragment_calender_dateMonth).text = "${converEnglishMonth(calendar.get(Calendar.MONTH)+1)} ${calendar.get(Calendar.YEAR)}"
val tmpMonth = calendar.get(Calendar.MONTH)
var dayList:MutableList<Date> = MutableList(6*7){Date()}
for(i in 0..5){
for(j in 0..6){
calendar.add(Calendar.DAY_OF_MONTH, (1-calendar.get(Calendar.DAY_OF_WEEK))+j)
dayList[i*7+j] = calendar.time
}
calendar.add(Calendar.WEEK_OF_MONTH,1)
}
var dayAdapter = DayAdapter(tmpMonth, dayList, date)
holder.layout.findViewById<RecyclerView>(R.id.fragment_calender_dayRv).apply {
layoutManager = GridLayoutManager(holder.layout.context,7)
adapter = dayAdapter
}
}
override fun getItemCount(): Int {
return Int.MAX_VALUE
}
fun converEnglishMonth(month:Int) :String{
if(month == 1){
return "January"
}
if(month == 2){
return "February"
}
if(month == 3){
return "March"
}
if(month == 4){
return "April"
}
if(month == 5){
return "May"
}
if(month == 6){
return "June"
}
if(month == 7){
return "July"
}
if(month == 8){
return "August"
}
if(month == 9){
return "September"
}
if(month == 10){
return "October"
}
if(month == 11){
return "November"
}
if(month == 12){
return "December"
}
return ""
}
}
여기서 DayAdapter를 연결했기 때문에 DayAdapter도 구현해야 합니다.
Step 5. DayAdapter 구현
package com.whitebear.customactivity
import android.graphics.Color
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import java.util.*
class DayAdapter(val tmpMonth:Int, val dayList:MutableList<Date>, val date: ArrayList<String>) : RecyclerView.Adapter<DayAdapter.DayViewHolder>(){
val ROW = 6
inner class DayViewHolder(val layout: View) : RecyclerView.ViewHolder(layout)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DayViewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_day,parent,false)
return DayViewHolder(view)
}
override fun onBindViewHolder(holder: DayViewHolder, position: Int) {
var day = holder.layout.findViewById<TextView>(R.id.fragment_calender_dayTv)
day.text = dayList[position].date.toString()
day.setTextColor(when(position%7){
0 -> Color.RED
6-> Color.BLUE
else -> Color.BLACK
})
if(tmpMonth != dayList[position].month){
day.alpha = 0.4f
}
//추가적으로 일정이 있는지 확인하는 구간
for(i in 0..date.size-1){
var month = date[i].substring(5,8).trim()
var monthOfday = date[i].substring(9,date[i].length-1).trim()
var strMonth = (dayList[position].month+1).toString()
var strDay = day.text.toString()
if(dayList[position].month.toString().length == 1){
strMonth = "0${strMonth}"
}
if(day.text.toString().length == 1){
strDay = "0${strDay}"
}
var strDate = "${strMonth}월 ${strDay}일"
var comDate = "${month}월 ${monthOfday}일"
var checkDay = day.text.toString()
if(checkDay.length == 1) {
checkDay = "0${checkDay}"
}
if(checkDay.equals(strDay)){
if(strDate.equals(comDate)){
holder.itemView.findViewById<ImageView>(R.id.fragment_calendar_point).visibility = View.VISIBLE
}
}
}
}
override fun getItemCount(): Int {
return ROW*7
}
}
📢 이 부분은 아까 만들었던 일정에 포인트를 주기 위한 과정입니다.
for(i in 0..date.size-1){
var month = date[i].substring(5,8).trim()
var monthOfday = date[i].substring(9,date[i].length-1).trim()
var strMonth = (dayList[position].month+1).toString()
var strDay = day.text.toString()
if(dayList[position].month.toString().length == 1){
strMonth = "0${strMonth}"
}
if(day.text.toString().length == 1){
strDay = "0${strDay}"
}
var strDate = "${strMonth}월 ${strDay}일"
var comDate = "${month}월 ${monthOfday}일"
var checkDay = day.text.toString()
if(checkDay.length == 1) {
checkDay = "0${checkDay}"
}
if(checkDay.equals(strDay)){
if(strDate.equals(comDate)){
holder.itemView.findViewById<ImageView>(R.id.fragment_calendar_point).visibility = View.VISIBLE
}
}
}
✨ 이 부분은 각자 입맛대로 하시면 될 것 같아요! 저는 DB에서 String 형으로 꺼내와서 비교하는 과정을 거쳤어서 쉽게 쓰려고 잘라서 사용했습니다!
Step 6. 마지막으로 Activity 또는 Fragment에서 Adapter를 연결해줍니다.
package com.whitebear.customactivity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import java.time.Month
class MainActivity : AppCompatActivity() {
private lateinit var monthAdapter: MonthAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setListener()
}
fun setListener(){
initCalendar()
}
fun initCalendar(){
var date = arrayListOf<String>("2022년 04월 01일","2021년 03월 28일","2021년 04월 09일","2021년 04월 18일")
monthAdapter = MonthAdapter(this, date)
findViewById<RecyclerView>(R.id.customCalendar).apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL,false)
adapter = monthAdapter
scrollToPosition(Int.MAX_VALUE/2)
}
val snap = PagerSnapHelper()
if(findViewById<RecyclerView>(R.id.customCalendar).onFlingListener == null){
snap.attachToRecyclerView(findViewById<RecyclerView>(R.id.customCalendar))
}
}
}
이렇게 하면 일반적인 CalendarView가 아닌 입맛대로 Custom 된 Calendar를 사용할 수 있습니다!
저처럼 Point를 넣는 것이 아닌 Recyclerview를 한번 더 넣는다면 라벨링도 할 수 있는 Calendar가 될 수 있어요!
😎😎😎
Full Code가 보고 싶다면?
https://github.com/leeboyeon/CustomUI/tree/main/CustomCalendar
GitHub - leeboyeon/CustomUI
Contribute to leeboyeon/CustomUI development by creating an account on GitHub.
github.com