본문 바로가기

안드로이드/Compose Dialog

[안드로이드 Jetpack Compose Dialog 시리즈] 커스텀 Dialog 구현하기 #1


안녕하세요. 이번 포스팅에서는 안드로이드 Jetpack Compose에서 Custom Dialog를 구현하는 방법에 대해서 설명하겠습니다. Compose는 선언적 UI 프레임워크로, UI를 구성하는 방식이 기존의 XML 기반 방식과는 크게 다릅니다. 이러한 차이로 인해 Dialog를 구현하는 방식에도 몇 가지 변화가 생겼습니다.

안드로이드 Jetpack Compose에서 구현한 커스텀 dialog
안드로이드 Jetpack Compose에서 구현한 커스텀 dialog

1. Compose에서 Dialog 구현하기

Jetpack Compose에서 Dialog를 사용하는 기본적인 방법은 Dialog 컴포저블을 직접 사용하는 것입니다. Dialog 컴포저블을 사용하면, Dialog의 기본적인 요소들이 전부 구현돼있는 것을 사용하면 됩니다. 그리고 Compose의 상태 관리 시스템과도 잘 통합되어 있습니다.

2. Custom Dialog 만들기

Compose에서 커스텀 Dialog를 만들기 위해서는 Dialog 컴포저블 내부에 원하는 레이아웃과 로직을 구현하면 됩니다. 여기에는 텍스트, 버튼, 이미지 등 원하는 모든 Compose UI 요소를 포함할 수 있습니다.

// CustomAlertDialog.kt
@Composable
fun CustomAlertDialog(
    title: String,
    description: String,
    onClickCancel: () -> Unit,
    onClickConfirm: () -> Unit
) {
    Dialog(
        onDismissRequest = { onClickCancel() },
        properties = DialogProperties(
            dismissOnBackPress = true,
            dismissOnClickOutside = true,
        )
    ) {
        Card(
            shape = RoundedCornerShape(8.dp), // Card의 모든 꼭지점에 8.dp의 둥근 모서리 적용
        )
        {
            Column(
                modifier = Modifier
                    .width(300.dp)
                    .wrapContentHeight()
                    .background(
                        color = Color.White,
                    ),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {

                Spacer(modifier = Modifier.height(40.dp))

                Text(
                    text = title,
                    textAlign = TextAlign.Center,
                    style = TextStyle(
                        color = Color.Black,
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Bold
                    )
                )

                Spacer(modifier = Modifier.height(10.dp))

                Text(
                    text = description,
                    textAlign = TextAlign.Center,
                    style = TextStyle(
                        color = Color.LightGray,
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Normal
                    )
                )

                Spacer(modifier = Modifier.height(40.dp))

                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(1.dp)
                        .background(color = Color.LightGray)
                )
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(IntrinsicSize.Min) // Row의 높이를 내부 컴포넌트에 맞춤
                ) {
                    Button(
                        onClick = { onClickCancel() },
                        shape = RectangleShape,
                        modifier = Modifier
                            .weight(1f)
                            .fillMaxHeight(),
                        colors = ButtonDefaults.buttonColors(
                            containerColor = Color.White, // 버튼 배경색상
                            contentColor = Color.Black, // 버튼 텍스트 색상
                            disabledContainerColor = Color.Gray, // 버튼 비활성화 배경 색상
                            disabledContentColor = Color.White, // 버튼 비활성화 텍스트 색상
                        ),

                        ) {
                        Text(
                            text = "취소",
                            textAlign = TextAlign.Center,
                            style = TextStyle(
                                fontSize = 14.sp,
                                fontWeight = FontWeight.Normal
                            )
                        )
                    }

                    Box(
                        modifier = Modifier
                            .fillMaxHeight()
                            .width(1.dp)
                            .background(color = Color.LightGray)
                    )

                    Button(
                        onClick = { onClickConfirm() },
                        shape = RectangleShape,
                        modifier = Modifier
                            .weight(1f)
                            .fillMaxHeight(),
                        colors = ButtonDefaults.buttonColors(
                            containerColor = Color.White, // 버튼 배경색상
                            contentColor = Color.Red, // 버튼 텍스트 색상
                            disabledContainerColor = Color.Gray, // 버튼 비활성화 배경 색상
                            disabledContentColor = Color.White, // 버튼 비활성화 텍스트 색상
                        ),
                    ) {
                        Text(
                            text = "삭제",
                            textAlign = TextAlign.Center,
                            style = TextStyle(
                                fontSize = 14.sp,
                                fontWeight = FontWeight.Bold
                            )
                        )
                    }
                }
            }
        }
    }
}

3. Custom Dialog 사용하

안드로이드 Jetpack Compose의 커스텀 Dialog를 사용하기 위해서는 상태 관리가 필요합니다. 여기서는 MutableState<CustomAlertDialogState> 타입의 상태를 통해 Dialog의 표시 여부를 제어했습니다. 이 상태는 ViewModel이나 Composable 함수 내부에서 관리될 수 있는데, ViewModel에서 제어하도록 구현했습니다.

// CustomAlertDialogState.kt
data class CustomAlertDialogState(
    val title: String = "",
    val description: String = "",
    val onClickConfirm: () -> Unit = {},
    val onClickCancel: () -> Unit = {},
)

// MainViewModel.kt
@HiltViewModel
class MainViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle?,
) : ViewModel() {
    val customAlertDialogState: MutableState<CustomAlertDialogState> = mutableStateOf<CustomAlertDialogState>(
        CustomAlertDialogState()
    )
    fun showCustomAlertDialog() {
        customAlertDialogState.value = CustomAlertDialogState(
            title = "정말로 삭제하시겠습니까?",
            description = "삭제하면 복구할 수 없습니다.",
            onClickConfirm = {
                resetDialogState()
            },
            onClickCancel = {
                resetDialogState()
            }
        )
    }
    // 다이얼로그 상태 초기화
    fun resetDialogState() {
        customAlertDialogState.value = CustomAlertDialogState()
    }
}
// MainScreen.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(
    mainNavController: NavHostController,
    viewModel: MainViewModel
) {

    val customAlertDialogState = viewModel.customAlertDialogState.value

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = "메인화면") },
                navigationIcon = {
                    IconButton(onClick = { mainNavController.popBackStack() }) {
                        Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
                    }
                }
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier.padding(paddingValues)
        ) {
            Button(
                onClick = { viewModel.showCustomAlertDialog() },
                shape = RectangleShape,
            ) {
                Text(
                    text = "1. Show AlertDialog",
                    textAlign = TextAlign.Center,
                    style = TextStyle(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Bold
                    )
                )
            }

            if (customAlertDialogState.title.isNotBlank()) {
                CustomAlertDialog(
                    title = customAlertDialogState.title,
                    description = customAlertDialogState.description,
                    onClickCancel = { customAlertDialogState.onClickCancel() },
                    onClickConfirm = { customAlertDialogState.onClickConfirm() }
                )
            }
        }
    }
}

4. Custom Dialog 정리

안드로이드 Jetpack Compose를 사용하여 커스텀 Dialog를 구현하는 방법을 살펴보았습니다. 기존의 XML 기반의 UI 개발과는 다른 접근 방식을 제공하지만, Compose의 선언적인 특성 덕분에 UI 상태 관리가 보다 간결하고 직관적으로 이루어집니다. Compose의 Dialog 컴포저블을 사용하여 다양한 커스텀 Dialog를 쉽게 구현할 수 있습니다.

다음 포스팅에서는 Jetpack Compose에서 BottomSheetDialog를 구현하겠습니다.

 

반응형