[ Android] Custom Dialog 사용 + 개발일지

[개발일지]

 

<Kotlin으로 개발하는 한 달 일정 관리 앱 개발>

 

앱 개발을 하던 중에 icon을 클릭 시 새로 변경할 icon을 선택하는 창이 필요했다.

Android의 Dialog 기능을 사용하려 했으나, 내가 원하는 대로 Custom을 해야 했다.

 

select icon
icon1 icon2
icon3 icon4

 

이런 형태로 icon 이미지를 선택하면 해당 이미지로 현재 todo Data의 icon을 변경하게 하고 싶었다.

 

우선 위의 표와 같은 ui를 보여주기 위해서 layout을 작성했다.

 

 

RecyclerView 내부의 listitem은 간단하게 icon 하나의 이미지를 나타낼수 있는 layout을 제작해서 연결했다.

 

이제 layout을 준비했으니 dialog를 띄울 class가 필요했다.

SelectIconDialog 클래스를 제작했다.

다른 activity나 fragment에서 이 클래스를 통해서 Custom Dialog를 띄울 수 있게 할 것이다.

 

private lateinit var binding: SelectIconDialogBinding
private val dig = Dialog(context)

 

앞에서 작성한 layout을 binding 해준후 Dialog 객체를 생성했다.

여기서 context는 AppCompatActivity를 파라미터로 전달 받는다.

 

class SelectIconDialog(private val context: AppCompatActivity) {

 

이제 앞에 제작한 각 item icon을 보여줄 Recycler View의 adapter을 제작한다.

 

private class IconAdapter(
) :RecyclerView.Adapter<IconAdapter.IconViewHolder>(){
    class IconViewHolder(val binding: ItemIconBinding):RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IconViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_icon,parent,false)
        return IconViewHolder(ItemIconBinding.bind(view))
    }

    override fun onBindViewHolder(holder: IconViewHolder, position: Int) {

        holder.binding.itemIconImage.setImageResource(calendarIcon[position])
    }

    override fun getItemCount(): Int = calendarIcon.size

}

 

아이콘의 이미지만 보여주면 되므로 매우 간단하게 코드를 작성했다.

여기서 calendarIcon은 drawable 이미지들이 저장된 list로 선택된 position을 인덱스로 이미지 값을 반환한다.

 

val calendarIcon :List<Int> = listOf(
    R.drawable.ic_iconmonstr_calendar_9,
    R.drawable.ic_baseline_directions_run_24,
    R.drawable.ic_baseline_sports_tennis_24,
    R.drawable.ic_iconmonstr_check_mark_13,
    R.drawable.ic_iconmonstr_pin_1,
    R.drawable.ic_iconmonstr_text_ckeck_lined
)

 

Recycler View의 0번째 position을 선택하면 calendarIcon의 0번 인덱스인 calendar_9 이미지로 변경된다.

 

이제 dig를 띄어줄 함수를 제작한다.

 

fun showDig() {
    binding = SelectIconDialogBinding.inflate(context.layoutInflater)
    dig.requestWindowFeature(Window.FEATURE_NO_TITLE) //타이틀 제거
    dig.setContentView(binding.root)
    dig.setCancelable(true)
    dig.window?.setBackgroundDrawableResource(R.drawable.round_border)
}

 

showDig()함수를 제작했다.

 

requestWindowFeature은 dig 객체의 타이틀을 제거한다.

setContentView는 binding된 layout과 dig의 view를 연결한다.

setCancelable은 true일 때 외부 화면 클릭 시 dig를 닫는 역할을 한다.

 

모든 세팅이 끝났으면 dig를 보여준다.

 

dig.show()

 

마지막으로 recycler View의 icon을 클릭 했을 때 dig view를 닫으면서 해당 icon으로 변경하는 코드가 필요했다.

Recycler View에 addOnItemTouchListener을 연결한다.

addOnItemTouchListener는 adapter 외부에서 item 클릭에 대한 이벤트를 처리하는데 도움을 준다.

 

addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
    override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
        val child = rv.findChildViewUnder(e.x, e.y)
        if (child != null) {
            val position = rv.getChildAdapterPosition(child)
            val view = rv.layoutManager?.findViewByPosition(position)
            view?.setOnClickListener {
                onClickedListener.onClicked(position)
                dig.dismiss()
            }
        }
        return false
    }

    override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
    override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
})

 

해당 item을 클릭하면 작성 된 click 이벤트로 position을 건내주며 dig를 종료하는 코드를 작성했다.

 

interface ButtonClickListener {
    fun onClicked(index: Int?)
}

private lateinit var onClickedListener: ButtonClickListener

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

 

Button Click에 대한 LIstener이다.

interface로 제작 되었으며 onClickedListener을 제작해서 interface를 상속 받도록 한다.

interface는 onClicked 메서드를 기능으로 가지고 있는데, 이는 클릭 이벤트 발생 시 position을 index로 받는 역할을 한다.

 

이제 모든 준비가 끝났으니 원하는 fragment나 activity에서 해당 dialog를 띄어준다.

 

 //icon 변경
holder.binding.calendarItemIcon.setOnClickListener {
    val dig = SelectIconDialog(mainActivity)
    dig.showDig()

    dig.setOnClickedListener(object :SelectIconDialog.ButtonClickListener{
        override fun onClicked(index: Int?) {
            if (index!=null){
                onClickChangeIcon.invoke(dataSet[holder.bindingAdapterPosition],index)
            }
        }

    })
 }

 

Recycler View 내부에서 해당 icon 클릭 시 icon을 변경하는 dialog를 띄우는 동작을 하는 코드이다.

새로 dig 객체를 생성하고, 작성해둔 showDig() 함수를 통해서 layout을 띄운다.

이후에 해당 dig에 대한 클릭 이벤트가 발생하면 이전에 제작한 interface를 상속받아서 

index에 해당하는 값으로 icon을 변경한다.

 

val onClickChangeIcon : (todo:Todo,index:Int)->Unit,

 

onClickChangeIcon은 dataSet에 position에 해당하는 값을 새로운 index로 변경하는 외부로 호출되는 함수이다.

 

onClickChangeIcon = { todo,index->
    mainActivity.viewModel.setIcon(todo,index)
}

 

adapter와 연결 된 Recycler View에서 icon 변경에 대한 데이터가 들어오고 viewmodel에 이를 적용하는 기능을 한다.

 

[적용 화면]

 

running icon 클릭

 

running이라는 todo Data의 icon을 클릭 시 앞에서 제작한 select Icon dialog가 나타난다.

 

1번째 position 클릭

 

1번째 인덱스(1번 positon - running icon)을 클릭하자 기존의 icon이 변경 된 것을 확인할 수 있다.