봄날은 갔다. 이제 그 정신으로 공부하자

Dialog 커스텀하기 본문

android Tip

Dialog 커스텀하기

길재의 그 정신으로 공부하자 2021. 1. 15. 11:01

이번 글에서는 android에서 기본으로 지원하는 AlertDialog를 활용하여 Dialog를 custom하는 방식에 대해 설명합니다.

저는 기본 AlertDialog가 좋은데 다른 사람들은 AlertDialog를 싫어하더라구요. ㅠ_ㅠ

 

 

일반 Dialog 만들기

android에서 지원하는 AlertDialog는 Builder(디자인 패턴의 Builder pattern 맞습니다.)를 지원하기 때문에  아래와 같이 간단히 dialog를 만드는 것이 가능합니다.

정말 심플하네요.

AlertDialog.Builder(this)
    .setTitle("TITLE")
    .setMessage("MESSAGE")
    .setNegativeButton("NO", { dialogInterface: DialogInterface, i: Int ->

    })
    .setPositiveButton("YES", { dialogInterface: DialogInterface, i: Int ->

    })
    .show()

 

Custom Dialog 만들기

Custom Dialog는 복잡하게 만들기보다는 일반적인 AlertDialog와 같이 Title과 Message가 있고 Yes & No 두개의 버튼만 만들어보겠습니다. 예제는 단순하지만 layout에서 만들어준 View를 dialog에 추가해주는 방식이다보니 List 등 다양한 방식으로 확장이 가능합니다.

 

loyout 만들기 

우선 dialog를 커스텀하기 위해서는 우선 Dialog에서 사용할 layoutxml을 만들어 줍니다.

위에서 언급한 것과 같이 Title과 Message 그리고 Yes & No 버튼 두개만 존재하는 기본적인 Dialog 형태를 띄고 있습니다.

<?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:layout_width="400dp"
    android:layout_height="wrap_content"
    android:padding="20dp">
    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_marginTop="10dp"
        android:textColor="@color/colorGrape"
        android:textSize="@dimen/dp28"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <View
        android:id="@+id/vDivider"
        android:layout_width="0dp"
        android:layout_height="5dp"
        android:layout_marginTop="20dp"
        android:background="@color/colorOatmeal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle"
        app:layout_constraintBottom_toTopOf="@+id/tvMessage"/>
    <TextView
        android:id="@+id/tvMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:gravity="center"
        android:layout_marginTop="50dp"
        android:layout_marginBottom="30dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:textColor="@color/colorGrape"
        android:textSize="@dimen/dp24"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/vDivider" />
    <Button
        android:id="@+id/btnNo"
        android:layout_width="0dp"
        android:layout_height="70dp"
        android:paddingStart="20dp"
        android:paddingLeft="20dp"
        android:paddingEnd="20dp"
        android:paddingRight="20dp"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:layout_marginTop="50dp"
        android:layout_marginRight="10dp"
        android:textSize="@dimen/dp24"
        android:textStyle="bold"
        android:textColor="@color/colorWhite"
        android:background="@drawable/button_round20_gray_white"
        android:stateListAnimator="@null"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btnYes"
        app:layout_constraintTop_toBottomOf="@+id/tvMessage" />
    <Button
        android:id="@+id/btnYes"
        android:layout_width="0dp"
        android:layout_height="70dp"
        android:paddingStart="20dp"
        android:paddingLeft="20dp"
        android:paddingEnd="20dp"
        android:paddingRight="20dp"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:layout_marginTop="50dp"
        android:layout_marginLeft="10dp"
        android:textSize="@dimen/dp24"
        android:textStyle="bold"
        android:textColor="@color/colorWhite"
        android:background="@drawable/button_round20_orange_tomato"
        android:stateListAnimator="@null"
        app:layout_constraintStart_toEndOf="@+id/btnNo"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

custom dialog 생성

커스텀 다이얼로그를 생성해주는 방법은 아래와 같습니다.

자세한 설명은 코드에 주석으로 추가하였습니다.

fun showCustomDialog(){
    // AlertDialog에 덧씌워줄 layout view를 생성합니다.
    val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
    val dialogView = inflater.inflate(R.layout.dialog_custom_yesno, null)

    // dialog에 view를 추가합니다.
    val dialog = AlertDialog.Builder(this)
            .setView(dialogView)
            .create()
    // dialog의 배경 이미지를 새로운 스타일로 변경합니다.
    dialog.window?.let {
        val windowLayoutParam = it.attributes
        windowLayoutParam.gravity = Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL
        it.attributes = windowLayoutParam
        it.setBackgroundDrawableResource(R.drawable.dra_round_white)
    }

    // Text를 입력합니다.
    dialogView.tvTitle.text = "Custom"
    dialogView.tvMessage.text = "My Custom dialog"

    // 버튼 Text와 이벤트를 정의해줍니다.
    dialogView.btnNo.text = "NO"
    dialogView.btnNo.setOnClickListener {
        Toast.makeText(this@MainActivity, "No Button Click", Toast.LENGTH_SHORT).show()
        dialog.dismiss()
    }

    dialogView.btnYes.text = "YES"
    dialogView.btnYes.setOnClickListener {
        Toast.makeText(this@MainActivity, "Yes Button Click", Toast.LENGTH_SHORT).show()
        dialog.dismiss()
    }

    // dialog가 종료 될때 처리가 필요한 경우 처리할 수 있도록 이벤트를 정의합니다.
    dialog?.setOnDismissListener {

    }

    // dialog를 사용자에게 보여줍니다.
    dialog.show()
}

 

Custom Dialog에 빌더 패턴 더하기

이렇게 만든 경우, 위 커스텀 다이얼로그를 생성할 때마다 위 언급된 긴 코드를 작성해야 하기 때문에 재사용성 및 기타 여러가지 측면에서 조금은 부족해보입니다.  재사용성 및 가독성 향상을 위해 커스텀 다이얼로그에 Builder 패턴을 적용해보도록 하겠습니다.

굳이 Builder 패턴을 적용해서 만들려는 이유는 일반적인 class로 만들 경우, 생성 과정이 복잡해 오히려 재사용성 및 가독성이 더 떨어질 수 있기 때문 입니다.

 

Custom Dialog class 만들기

아래와 같이 빌더 패턴을 적용한 MyCustomDialog class를 만들어줍니다.

class MyCustomDialog {
    data class Builder(
            var context: Context? = null,
            var textTitle: String = "",
            var textMessage: String = "",
            var textNo: String = "",
            var textYes: String = "",
            var onFinished: (()->Unit)? = null,
            var onClickNo: ((AlertDialog)->Unit)? = null,
            var onClickYes: ((AlertDialog)->Unit)? = null
    ){
        lateinit var dialog: AlertDialog
        fun context(context: Context) = apply { this.context = context }
        fun setTitle(textTitle: String) = apply { this.textTitle = textTitle }
        fun setMessage(textMessage: String) = apply { this.textMessage = textMessage }
        fun setOnFinished(onFinished: (() -> Unit)) = apply { this.onFinished = onFinished }
        fun setOnClickNo(textNo: String, onClickNo: ((AlertDialog) -> Unit)) = apply {
            this.textNo = textNo
            this.onClickNo = onClickNo
        }
        fun setOnClickYes(textYes: String, onClickYes: ((AlertDialog) -> Unit)) = apply {
            this.textYes = textYes
            this.onClickYes = onClickYes
        }
        fun build(): AlertDialog{
            context?.run {
                val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
                val dialogView = inflater.inflate(R.layout.dialog_custom_yesno, null)
                dialog = AlertDialog.Builder(this)
                        .setView(dialogView)
                        .create()
                dialog?.window?.let {
                    val windowLayoutParam = it.attributes
                    windowLayoutParam.gravity = Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL
                    it.attributes = windowLayoutParam
                    it.setBackgroundDrawableResource(R.drawable.dra_round_white)
                }

                dialogView.tvTitle.text = textTitle
                dialogView.tvMessage.text = textMessage
                if(textNo.isNullOrEmpty()){
                    dialogView.btnNo.visibility = View.GONE
                }else {
                    dialogView.btnNo.visibility = View.VISIBLE
                    dialogView.btnNo.text = textNo
                    dialogView.btnNo.setOnClickListener {
                        onClickNo?.invoke(dialog)
                    }
                }
                if(textYes.isNullOrEmpty()){
                    dialogView.btnYes.visibility = View.GONE
                }else {
                    dialogView.btnYes.visibility = View.VISIBLE
                    dialogView.btnYes.text = textYes
                    dialogView.btnYes.setOnClickListener {
                        onClickYes?.invoke(dialog)
                    }
                }

                dialog?.setOnDismissListener {
                    onFinished?.invoke()
                }
            }

            return dialog
        }

    }
}

 

Custom Dialog 사용하기

위와 같이 만들어진 Custom Dialog는 아래와 같이 사용하면 됩니다.

아래 코드를 보면 기존 사용하던 AlertDialog와 유사한 것을 확인하실 수 있습니다.

 

MyCustomDialog.Builder()

        .context(this@MainActivity)

        .setTitle("Custom")

        .setMessage("My Custom dialog")

        .setOnClickNo("NO", {

            Toast.makeText(this@MainActivity, "No Button Click", Toast.LENGTH_SHORT).show()

            it.dismiss()

        })

        .setOnClickYes("YES", {

            Toast.makeText(this@MainActivity, "Yes Button Click", Toast.LENGTH_SHORT).show()

            it.dismiss()

        })

        .setOnFinished {

 

        }.build()

        .show()

 

정리

위 정리한 것처럼 생성 시 파라미터가 여러개 들어가 생성이 복잡한 instance를 만들때는 Builder 패턴을 사용해주는것이 좋습니다.

 

위 코드는 아래 링크에서 확인하실 수 있습니다.

github.com/lee-kil-jae/MyListCollection

 

lee-kil-jae/MyListCollection

ListCollection. Contribute to lee-kil-jae/MyListCollection development by creating an account on GitHub.

github.com

 

'android Tip' 카테고리의 다른 글

앱 재실행 (App Restart)  (0) 2021.01.22
PopupWindow로 Spinner 대체하기  (0) 2021.01.18
Callback Java에서 Kotlin으로…  (0) 2021.01.13
여러가지 방식으로 List 만들어보기 - OldStyle  (0) 2021.01.11
Coroutine - part2  (0) 2020.12.28
Comments