[Android] 캘린더 년과 달만 변경하기, NumberPicker 사용 + 개발일지

 

[필요 기능!]

 

원하는 년도와 달을 선택해서 해당 데이터를 불러오는 기능

 

 

 

 

위의 영상처럼 해당 년도의 달을 선택하면 그 달의 일자를 불러오고, 일자별 task List를 표시하는 기능을 개발 중이었다.

버튼을 클릭하면 연도와 달을 선택하는 coustom dialog view가 필요했다.

 

<month_picker.xml>

 

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/item_height_double_big"
    android:layout_height="@dimen/item_height_double_big"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@drawable/round_border">
    <LinearLayout
        android:gravity="center"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/month_picker_bottom"
        android:layout_width="@dimen/item_height_big_180"
        android:layout_height="@dimen/item_height_big_150">
        <NumberPicker
            android:id="@+id/year_picker"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <NumberPicker
            android:id="@+id/month_picker"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
        android:id="@+id/month_picker_bottom"
        android:layout_marginBottom="@dimen/main_margin"
        android:padding="@dimen/main_margin"
        app:layout_constraintBottom_toBottomOf="parent"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_height">
        <Button
            android:id="@+id/btn_cancel"
            android:layout_marginEnd="@dimen/main_margin"
            android:text ="Cancel"
            android:backgroundTint="@color/color_type2"
            android:src="@drawable/round_border"
            android:padding="@dimen/main_margin"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="@dimen/item_height"/>
        <Button
            android:id="@+id/btn_set"
            android:text ="Set"
            android:backgroundTint="@color/color_type2"
            android:src="@drawable/round_border"
            android:padding="@dimen/main_margin"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="@dimen/item_height"/>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

아래와 같은 view를 그려준다. 

 

NumberPicker를 사용하였는데 설정된 범위에서 원하는 값을 선택할 수 있으며, 초기값도 지정할 수 있다.

현재 년도와 달을 초기값으로 설정하고, 선택된 값을 데이터로 전달한다.

 

dialog를 띄어주기 위한 클래스를 선언한다.

 

class MonthPickerDialog(
    private val year:Int,
    private val month:Int,
) :DialogFragment() {

 

DialogFragment를 상속해서 month_picker view에 대한 데이터를 지정한다.

 

var btnSet: Button? = null
var btnCancel: Button? = null
var monthPicker: NumberPicker? = null
var yearPicker : NumberPicker? = null

 

위에서 만들어준 xml의 데이터들을 선언해준다.

차례대로 ok버튼, 취소 버튼, 월 선택 number picker, 년도 선택 number picker이다.

 

onCreateDialog를 통해  activity 내부에서 dialog를 생성한다.

해당 함수를 override 해준다.

 

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog

 

builder와 inflater를 선언한다.

bulider를 통해서 view를 그려주고, inflater를 통해서 view의 데이터에 접근할 수 있다.

 

val builder  = AlertDialog.Builder(activity)
val inflater = requireActivity().layoutInflater

 

inflater를 통해서 dialog view를 선언하고, 위의 변수들을 초기화한다.

 

 

val dialog : View = inflater.inflate(R.layout.month_picker,null).also {
    btnSet = it.findViewById<Button>(R.id.btn_set)
    btnCancel = it.findViewById<Button>(R.id.btn_cancel)
    monthPicker = it.findViewById<View>(R.id.month_picker) as NumberPicker
    yearPicker = it.findViewById<View>(R.id.year_picker) as NumberPicker
}

 

이제 각 view에 대한 값이나 클릭 이벤트를 설정한다.

 

btnSet?.setOnClickListener{
    onClickedListener.onClicked(yearPicker!!.value,monthPicker!!.value)
    dismiss()
}

btnCancel?.setOnClickListener{
    dismiss()
}

monthPicker?.apply {
    minValue = 1
    maxValue = 12
    value = month
}
yearPicker?.apply {
    minValue = 2010
    maxValue = 2099
    value = year
}

 

numberPicker에서는 minValue, maxValue, value를 설정해주었는데 최솟값, 최댓값, 기본값을 설정한다 

기본값으로는 클래스가 선언될 때 전달받은 년도와 달로 설정해준다.

 

값 세팅이 완료되었으면 builder를 통해서 view를 띄어준다.

 

builder.setView(dialog)
return builder.create()

 

set 버튼이 클릭되었을 경우 onClickedListener를 발생하도록 했다.

 

interface ButtonClickListener {
    fun onClicked(year:Int,month:Int)
}

private lateinit var onClickedListener: ButtonClickListener

fun setOnClickedListener(listener: ButtonClickListener) {
    onClickedListener = listener
}

 

위와 같이 interface를 선언하고 버튼이 클릭 되었을 때의 이벤트를 설정한다.

 

 

이제 fragment나 activity에서 view를 띄어주는 코드를 작성한다. 

dig 클래스를 선언하고 show()를 해당 클래스를 보여준다.

fragment에서 show()를 사용하기 위해서는 두 가지 인자가 필요하다.

 

1. fragment manger 

 

fragment manager는 mainActivity에서 supportFragmentManager를 받아온다.

 

private lateinit var fm : FragmentManager

 

onAttach에서 초기화해주었다. 

 

override fun onAttach(context: Context) {
    super.onAttach(context)
    mainActivity = context as MainActivity
    fm = (activity as MainActivity).supportFragmentManager
}

 

2. tag

 

tag는 고유번호 같은 것인데 필요하지 않다면 null을 넣어준다.

 

binding.taskListCalendar.setOnClickListener {
    val dig = MonthPickerDialog(mainActivity.viewModel.currentYear.value!!,mainActivity.viewModel.currentMonth.value!!)
    dig.show(fm,null)
    dig.setOnClickedListener(object : MonthPickerDialog.ButtonClickListener{
        override fun onClicked(year: Int, month: Int) {
            changeCalendar(year,month)
        }
    })
}

 

이제 원하는 dialog를 얻을 수 있다.

set 버튼에 대한 클릭 이벤트가 발생할 경우 새로운 year, month 값을 받아와서 changeCalendar함수를 실행한다.

 

private fun changeCalendar(year:Int, month:Int){
    mainActivity.viewModel.apply {
        initCurrentData(getDate(year,month))
        binding.taskListCalendar.text = this.dateTime
    }
    initRecyclerView()
    initAni()
}

 

changeCalendar 함수는 viewModel에서 현재 달에 대한 정보를 갱신해준다.

데이터가 새로 갱신되었으니 recyclerview를 새로 그려주고, 애니메이션을 적용했다.