커스텀 달력 구현을 위해 자료를 찾던 중 Medium에서 참조할만한 레퍼런스를 찾았으나
자바코드로 구성되어 있는것은 물론 일부 생략된 부분들이 있어
구현에 조금 어려움이 있었다.
우여곡절 끝에 기본구성을 마치고 구성에 필요했던 과정과 준비물들을 정리해두고자 한다.
준비물
1. 뷰로 선언할 캘린더의 xml Layout (calendar_layout)
2. 달력의 일자에 적용할 xml Layout (calendar_day_layout)
3. LinearLayout을 확장해 만든 커스텀 캘린더 클래스
4. 캘린더의 일자 구성, 색상변경등을 수행할 캘린더 어댑터 클래스
구성 방법
1. 뷰로 선언할 캘린더의 xml Layout (calendar_layout) 만들기
캘린더의 전체 틀로써 크게 LinearLayout으로 짠 요일 섹션과 GridView를 사용한 일자 섹션으로 구성됨.
요일 섹션의 경우 사용자의 입맛에 따라 디자인을 하면 되며, 가장 상위 레이아웃의 경우 필자는 ConstraintLayout을 사용하였음.
<?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"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/calendar_header"
android:layout_width="match_parent"
android:layout_height="30dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- 달력의 요일 섹션 구성 -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:textColor="#222222"
android:text="MON"/>
...일요일까지 반복.
구성하고자 하는 달력 스타일에 따라 커스터마이징 할 것.
</LinearLayout>
<!-- 달력의 일자 섹션 구성 -->
<GridView
android:id="@+id/calendar_grid"
android:layout_width="match_parent"
android:layout_height="240dp"
android:numColumns="7"/>
</androidx.constraintlayout.widget.ConstraintLayout>
2. 달력의 일자에 적용할 xml Layout (calendar_day_layout)
달력의 개별 일자 디자인을 설정하는 xml 레이아웃. TextView로 선언해야 함.
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:layout_width="38dp"
android:layout_height="38dp"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:text="1"
android:gravity="center"
android:textSize="22sp"
android:padding="5dp"
android:layout_margin="15dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
3. LinearLayout을 확장해 만든 커스텀 캘린더 클래스
커스텀 캘린더 클래스는 커스텀 캘린더 어댑터와 함께 달력의 날짜를 어떻게 정의하고 디자인 할지를 지정하게 된다.
class CalendarView: LinearLayout {
lateinit var header: LinearLayout
lateinit var gridView: GridView
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context,
attrs, 0) {
initControl(context, attrs!!)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private fun assignUiElements() {
// layout is inflated, assign local variables to components
header = findViewById(R.id.calendar_header)
gridView = findViewById(R.id.calendar_grid)
}
fun updateCalendar(events: HashSet<Date>, inputCalendar: Calendar) {
val cells = ArrayList<Date>()
inputCalendar.set(Calendar.DAY_OF_MONTH, 1)
// 여기서 빼주는 값 1의 경우 한 주의 시작요일에 따라 다르게 설정해주면 됨.
// 필자가 쓴 캘린더의 경우 일요일부터 시작하는 관계로 1을 감산해주었음.
val monthBeginningCell = inputCalendar.get(Calendar.DAY_OF_WEEK) - 1
inputCalendar.add(Calendar.DAY_OF_MONTH, -monthBeginningCell)
// 그리드에 집어넣을 cell들의 setup.
while (cells.size < (Calendar.DAY_OF_MONTH) +
inputCalendar.getActualMaximum(Calendar.DAY_OF_MONTH)) {
cells.add(inputCalendar.time)
inputCalendar.add(Calendar.DAY_OF_MONTH, 1)
}
// 그리드 업데이트
gridView.adapter = CalendarAdapter(context, cells, events, inputCalendar.get(Calendar.MONTH))
}
private fun initControl(context: Context, attrs: AttributeSet) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.calendar_layout, this)
assignUiElements()
}
4. 캘린더의 일자 구성, 색상변경등을 수행할 캘린더 어댑터 클래스
커스텀 캘린더의 앞단을 마무리 짓는 클래스로, CalendarAdapter의 생성과 함께 넘어온 day ArrayList와
inputMonth를 활용해 ViewPager에 보여지는 월에 해당하는 일자만을 보여주도록 설정하게 된다.
+ 여기서 ViewPager를 쓰지 않을 경우에는 물론 inputMonth를 활용하지 않아도 될 것
class CalendarAdapter(context: Context, days: ArrayList<Date>, eventDays: HashSet<Date>,
inputMonth: Int) :
ArrayAdapter<Date>(context, R.layout.calendar_layout, days) {
// for view inflation
private val inflater: LayoutInflater = LayoutInflater.from(context)
private val inputMonth = inputMonth - 1
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
var view = view
val calendar = Calendar.getInstance()
val date = getItem(position)
calendar.time = date
val day = calendar.get(Calendar.DATE)
val month = calendar.get(Calendar.MONTH)
val year = calendar.get(Calendar.YEAR)
// 오늘에 해당하는 캘린더를 가져옴
val today = Date()
val calendarToday = Calendar.getInstance()
calendarToday.time = today
// 날짜 디자인으로 먼저 만들어 둔 calendar_day_layout을 inflate
if (view == null) {
view = inflater.inflate(R.layout.calendar_day_layout, parent, false)
}
// 여기에서 기호에 따라 뷰의 생김새와 일자의 디자인을 변경이 가능.
(view as TextView).setTypeface(null, Typeface.NORMAL)
view.setTextColor(Color.parseColor("#56a6a9"))
// inputMonth는 ViewPager의 해당 페이지에 출력하는 Month를 표시.
if (month != inputMonth || year != calendarToday.get(Calendar.YEAR)) {
// 아래의 경우 해당월이 아닌 경우에는 GridView에 표시되지 않도록 설정한 예.
view.visibility = View.INVISIBLE
}
if (month == calendarToday.get(Calendar.MONTH) && year == calendarToday.get(Calendar.YEAR) &&
day == calendarToday.get(Calendar.DATE)) {
// 오늘의 날짜에 하고싶은 짓(?)을 정의
}
// 날짜를 텍스트뷰에 설정
view.text = calendar.get(Calendar.DATE).toString()
return view
}
}
생각보다 일자 설정과 기본적인 레이아웃 구성에 시간이 걸려 로드가 있던 작업이었는데,
한번 마무리짓고 나니 다양하게 설정해 쓸 수 있는 캘린더의 토대를 얻었다는 생각이 든다.
다만 캘린더 페이지로 들어갈 때와 ViewPager에서 페이지를 넘길 때에 생기는 약간의 버퍼가 있어
최적화가 필요한 부분은 최적화가 들어가야되지 않을까 싶다.
기본적인 커스텀 캘린더 설정은 위에서 다루었고, 이를 ViewPager에 접목하고자 활용한 과정은 아래와 같다.
1. 설정은 똑같이 하되 개발을 하면서 쓴 커스텀 캘린더를 ViewPager의 페이지 레이아웃에 선언하고
2. ViewPagerAdapter의 instatiateItem에 페이지 변경시마다 변경된 페이지에 해당하는 월을 설정하게 되면 마무리된다.
2-1. 여기에서 필자는 캘린더의 월 설정을 위해 최초 페이지를 ViewPager의 마지막 페이지로 설정하고
2-2. 현재의 월과 페이지 변경으로 인해 바뀌는 월의 차이를 캘린더 뷰에 적용하는 것으로 구현을 마칠 수 있었다.
override fun instantiateItem(container: ViewGroup, position: Int): Any{
val eventDays: HashSet<Date> = HashSet()
eventDays.add(Date())
// monthGap은 현재 월과의 차이를 저장.
monthGap = pageCount - 1 - position
val calendar = Calendar.getInstance()
calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) - monthGap)
view.findViewById<CalendarView>(R.id.calendar_view).updateCalendar(eventDays,calendar)
container.addView(view)
}
** ViewPager 설정에 관해 보고자 하면 아래 페이지를 슬쩍 살펴봐보자 **
https://mparchive.tistory.com/138
< 참조 >
https://medium.com/meetu-engineering/create-your-custom-calendar-view-10ff41f39bfe
'ANDROID > UI - UX' 카테고리의 다른 글
[안드로이드] ConstraintLayout + CollapsingToolbarLayout 활용해 접히는 TabBar 구현하기 (0) | 2020.01.29 |
---|---|
[안드로이드] RecyclerView에서 클릭한 아이템의 position을 간단히 받아오기 (0) | 2019.11.13 |
[안드로이드 | MpAndroidChart] 바 그래프의 하단 영역이 생길 때의 대처법 (0) | 2019.10.30 |
[안드로이드 | 코틀린] 데이터바인딩을 사용할 때 Fragment 내의 View 좌표 가져오기 (0) | 2019.10.28 |
[안드로이드] ConstraintLayout 내부에 Fragment 알맞게 배치하기 (0) | 2019.10.15 |