[Android] 캘린더에 일정 표시하기 + 개발일지

 

 

[필요 기능! ]

 

캘린더에 일정을 표시하는 기능이 필요했다.

 

 

위의 이미지처럼 새로운 task를 추가할 때 달력에 해당 일정이 표시되도록 하는 기능이 필요했다.

각 달력의 일자는 SelectCalendarFragment의  Recycler View인 calendarViewPager에서 나타나며.

CalendarAdapter로 연결된다.

 

시작일, 종료일을 지정하기 위해서 2가지 변수를 선언한다.

 

var startNum = -1
var endNum = -1

 

각 item에 대한 클릭 이벤트가 필요하다, OnItemClickListener를 interface로 선언한다.

 

interface OnItemClickListener {
    fun onItemClick()
}

 

아이템 클릭 리스너를 선언하고, 인터페이스와 연결해준다.

 

private var onItemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
    this.onItemClickListener = listener
}

 

이제 해당 아이템에 대한 클릭 이벤트가 발생했을 경우 onItemClick을 통해서 해당 포지션의 데이터에 대한 처리가 된다.

 

달력의 각 날짜에 미리 선택 이미지 뷰를 띄어준 후에,

클릭 이벤트에 따라서 뷰를 나타낼지, 숨길지 결정하는 방식을 사용했다.

 

private lateinit var binding: ItemCalendarBinding

 

<item_calendar.xml>

 

<TextView
    android:id="@+id/calendar_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal|top"
    android:textColor="@color/strong_color"
    android:textSize="@dimen/item_height_mid_text_size"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:text="1" />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:gravity="center"
    android:orientation="horizontal"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/calendar_text">

    <ImageView
        android:id="@+id/item_line_mid"
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_mid_height_30"
        android:background="@drawable/task_line_mid"
        android:visibility="gone" />
</LinearLayout>

 

달력의 각 일자를 표시하는 item인데, LinearLayout 안에 Image View를 넣어준다.

item_line_mid의 이미지를 각 조건에 맞게 나타내는 코드가 필요했다.

 

 

3가지 선택 조건이 필요했다.

 

1. 하루 일정을 추가하는 경우

2. 여러 날의 일정을 한번에 추가하는 경우

3. 일정 선택을 리셋하는 경우

 

 

하루 일정을 추가하는 경우에는 하나의 circle이미지만 나타내면 된다.

 

private fun ItemCalendarBinding.setOneSelected() {
    itemLineMid.visibility = View.VISIBLE
    itemLineMid.setBackgroundResource(R.drawable.task_line_circle)
}

 

startNum이 절댓값 position을 나타내는 absoluteAdapterPosition과 같은 경우는 

하나의 일정이 추가되거나 여러 날의 일정이 추가된 경우 첫 번째 값이다.

 

일정을 표시할 때 하나의 뷰만 선택 된 경우: ()

여러 뷰가 선택된 경우 : (| [        ] |) 

 

하나의 뷰가 선택된 경우는 단순히 circle 이미지를 추가하면 되지만,

두 개 이상의 뷰가 선택 된 경우 3가지의 이미지가 필요했다.

 

1. 첫 이미지는 오른쪽으로 열린 반 원  : (|

2. 마지막 이미지는 왼쪽으로 열린  반 원 : |)

3. 사이의 이미지는 긴 사각형  : [   ] 

 

이를 위해 아래와 같은 코드를 작성했다.

 

private fun ItemCalendarBinding.setHead() {
    itemLineMid.visibility = View.VISIBLE
    itemLineMid.setBackgroundResource(R.drawable.task_line_start)
}

private fun ItemCalendarBinding.setMid() {
    itemLineMid.visibility = View.VISIBLE
    itemLineMid.setBackgroundResource(R.drawable.task_line_mid)
}
private fun ItemCalendarBinding.setTail() {
    itemLineMid.visibility = View.VISIBLE
    itemLineMid.setBackgroundResource(R.drawable.task_line_end)
}

 

setHead인 경우 1번 조건, setTail인 경우 2번 조건, setMid인 경우 3번 조건을 적용해서 다른 이미지 뷰를 보여준다.

 

뷰의 이미지를 감추기 위한 뷰 reset 함수도 만들어 준다.

 

private fun ItemCalendarBinding.reset() {
    itemLineMid.visibility = View.GONE
}

 

이제 선택 조건에 따라서 적절하게 위의 이미지 뷰를 설정하는 함수를 실행하면 된다.

 

startNum 이 절댓값 인덱스인 absoluteAdapterPosition인 경우는 2가지 경우이다.

 

1 endNum이 선택 된 경우

2. endNum이 선택 되지 않은 경우

 

1의 경우는 여러 아이템이 선택 되었는데, 그중 현재 뷰는 맨 처음 선택된 뷰이므로 setHead()를 실행한다.

2의 경우는 하나의 아이템만 선택된 경우이므로  setOneSelected()를 실행한다.

 

endNum 이 절댓값 인덱스인 absoluteAdapterPosition인 경우는 마지막 선택된 뷰이므로 setTail()를 실행한다.

 

if (endNum==absoluteAdapterPosition){
    binding.setTail()
    mainActivity.addViewModel.endNum=endNum
}

 

만약 endNum,startNum이 초기값인 -1이 아니고, startNum과 endNum사이에 absoluteAdapterPosition이 있는 경우는

몸통인 경우이므로 setMid() 함수를 실행한다.

 

if (endNum!=-1 && startNum!=-1 &&
    absoluteAdapterPosition>startNum && absoluteAdapterPosition<endNum){
    binding.setMid()
}

 

이 외의 경우에는 뷰가 가려져야 하므로 reset()을 실행한다,

 

//클릭 조건
if (startNum==-1 && endNum==-1) {
    binding.reset()
    mainActivity.addViewModel.startNum=-1
    mainActivity.addViewModel.endNum=-1
}

 

이제 클릭 이벤트를 설정해야 한다. 

 

//클릭 이벤트
if (onItemClickListener != null) {
    binding.root.setOnClickListener {

 

위에서 설정한 클릭 이벤트가 null값이 아닌 경우 뷰에 대한 클릭 이벤트를 실행한다.

 

onItemClickListener?.onItemClick()

 

item click 이벤트를 adapter 외부로 보내준다.

여기서 onItemClick 이벤트에 인자를 설정할 경우 값을 외부로 전달할 수도 있다.

 

이제 앞선 조건들을 만족하는 클릭 이벤트를 설정한다.

 

두 값이 모두 설정한 경우는 3번 조건인 일정 선택을 리셋하는 경우이다.

 

if (startNum!=-1 && endNum!=-1){
    startNum = -1
    endNum = -1
}

 

startNum만 설정 된 경우는 1번 조건인 하루 일정을 추가하는 경우이다.

startNum과 endNum이 설정된 경우는 2번 조건인 여러 날의 일정을 추가하는 경우이다.

 

else{
    if (startNum==-1){
        startNum = absoluteAdapterPosition
    }else if ( startNum!=-1 && endNum==-1){
        if (absoluteAdapterPosition != startNum) {
            endNum = absoluteAdapterPosition
            if (startNum > endNum) {
                val temp = startNum
                startNum = endNum
                endNum = temp
            }
        }
    }
}

 

마지막으로 item View가 변경 되었으므로 변경 사항을 알려준다.

 

notifyDataSetChanged()

 

이제 1,2,3 조건에 대한 클릭 이벤트가 정상적으로 동작하는지 확인한다.

 

1. 하나의 뷰 선택 

 

 

2. 여러 뷰 선택 

 

 

3. 초기화 

 

 

꿑