Jetpack Compose 로 커스텀 다이얼로그 만들기

김단희
Team Rocket
Published in
11 min readNov 28, 2023

명령형 기반 뷰 시스템에서의 Dialog 부터 선언형 기반 Dialog 에 이르기까지

안드로이드 앱을 개발하다 보면 Dialog 형태의 UI 를.
특히 커스텀 해서 그려야 하는 상황을 마주하게 되는데요,

Custom Dialog 예시

오늘은 선언형의 시대에 온 우리가 Jetpack Compose 를 이용하여 커스텀 다이얼로그를 만드는 방법을 살펴보겠습니다.

Compose 등장 이전 다이얼로그를 그리기 위한 방법으로 가장 간단하게는 안드로이드 기본 sdk 에서 제공되는 android.app.AlertDialog 를 사용하는 방법이 있었습니다

명령형 형태로 Builder 를 통해 생성한 Dialog 객체에 필요한 속성들을 (제목, 메시지, …) setter 함수들을 통해 설정합니다.

AlertDialog.Builder(context)
.setTitle("$title")
.setMessage("$description")
.setPositiveButton("${button_text}") { dialog, which ->
// 긍정 버튼 클릭 동작 처리
}
.setNegativeButton("${button_text}") { dialog, which ->
// 부정 버튼 클릭 동작 처리
}
.show()

커스텀 형태의 Dialog 를 위해서는 해당 Dialog 를 위한 layout xml 파일 작성 후 해당 layout 을 inflate 하여 AlertDialog.Builder#setView 함수를 통해 처리할 수 있습니다.

val dialogView = LayoutDialogBinding.inflate(...).root.apply {
// 커스텀 다이얼로그 자식 뷰들 (타이틀 뷰, 메시지 뷰, 버튼 ...) 속성 설정
}

AlertDialog.Builder(context)
.setView(dialogView)
.show()

간단하게 기존 명령형 기반 뷰 시스템에서의 다이얼로그 그리는 대해 알아보았고 이제 compose 를 통해 다이얼로그를 그리는 방법을 알아보겠습니다.

기존 뷰 시스템에서와 마찬가지로 가장 간단하게 다이얼로그를 그리는 방법은 미리 정의된 AlertDialog composable 을 사용하는 방법 입니다.

AlertDialog composable 은 compose sdk 에서 다음과 같이 제공됩니다

@Composable
fun AlertDialog(
onDismissRequest: () -> Unit,
confirmButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
dismissButton: @Composable (() -> Unit)? = null,
title: @Composable (() -> Unit)? = null,
text: @Composable (() -> Unit)? = null,
shape: Shape = MaterialTheme.shapes.medium,
backgroundColor: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(backgroundColor),
properties: DialogProperties = DialogProperties()
)

다음 코드를 통해 간단한 알림 대화상자 Preview 를 확인할 수 있습니다

@Composable
fun AlertDialogExample(
onDismiss: () -> Unit,
onConfirmation: () -> Unit,
dialogTitle: String,
dialogText: String,
icon: ImageVector,
) {
AlertDialog(
icon = { Icon(icon, contentDescription = null) },
title = { Text(text = dialogTitle) },
text = { Text(text = dialogText) },
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = onConfirmation) {
Text("Confirm")
}
},
dismissButton = {
Button(onClick = onDismissRequest) {
Text("Dismiss")
}
}
)
}

@Preview
@Composable
fun AlertDialogExamplePreview() {
AlertDialogExample(
onDismiss = {},
onConfirmation = {},
dialogTitle = "Alert dialog example",
dialogText = "This is an example of an alert dialog with buttons.",
icon = Icons.Default.Info
)
}
간단한 알림 대화상자 Preview

보다 커스텀하게 Dialog 를 사용하기 위해서는 Dialog composable 을 사용할 수 있습니다.

@Composable
fun Dialog(
onDismissRequest: () -> Unit,
properties: DialogProperties = DialogProperties(),
content: @Composable () -> Unit
)

properties 파라미터로 DialogProperties 객체가 넘어가는데 해당 객체를 통해 다음 Dialog 설정들이 가능합니다

@Immutable
class DialogProperties constructor(
// 물리 백 키 클릭시 dismiss 처리 여부
val dismissOnBackPress: Boolean = true,
// 대화상자 외부 클릭시 dismiss 처리 여부
val dismissOnClickOutside: Boolean = true,
val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
// 대화상자 콘텐츠의 너비를 화면 너비보다 작은 플랫폼 기본값으로 제한해야 하는지 여부
val usePlatformDefaultWidth: Boolean = true,
val decorFitsSystemWindows: Boolean = true
)

content 파라미터에 대화상자를 구성할 하위 composable 을 전달함으로써 커스텀하게 Dialog 를 구성할 수 있습니다

다음 코드를 통해 최소한의 대화상자를 구현해 볼 수 있습니다

@Composable
fun MinimalDialog(onDismiss: () -> Unit) {
Dialog(onDismissRequest = onDismiss) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Text(
text = "This is a minimal dialog",
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center),
textAlign = TextAlign.Center,
)
}
}
}

@Preview
@Composable
fun MinimalDialogPreview() {
MinimalDialog(onDismiss = {})
}
최소한의 대화상자

다음 코드를 통해 보다 복잡한 형태의 대화상자를 커스텀하게 구현할 수 있습니다

@Composable
fun FDSDialogContent(
title: String,
description: String = "",
positiveText: String,
negativeText: String,
onClickOk: () -> Unit,
onClickNo: () -> Unit
) {
Column(
modifier = Modifier
.padding(50.dp)
.background(color = Background20, shape = RoundedCornerShape(8.dp))
.padding(horizontal = 24.dp, vertical = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
SubTitleXLarge(
text = title,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 8.dp)
)
if (description.isNotBlank()) {
Spacer(modifier = Modifier.size(4.dp))
BodySmall(
text = description,
modifier = Modifier.padding(bottom = 8.dp),
color = Gray70,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.size(20.dp))
Row(modifier = Modifier.fillMaxWidth()) {
if (negativeText.isNotBlank()) {
SubTitleXLarge(
text = negativeText,
modifier = Modifier
.weight(1f)
.clickable { onClickNo() },
color = TextColorTertiary,
textAlign = TextAlign.Center,
)
}
SubTitleXLarge(
text = positiveText,
modifier = Modifier
.weight(1f)
.clickable { onClickOk() },
color = TextColorOrange,
textAlign = TextAlign.Center,
)
}
}
}

다음과 같이 호출하여 예시 이미지와 같은 대화상자를 확인할 수 있습니다

FDSDialogContent(
title = "10분 동안 잠글게요",
description = "잠긴 시간 동안 취소하기 어려워요",
positiveText = "잠그기",
negativeText = "돌아가기",
onClickOk = {},
onClickNo = {}
)
Custom Dialog 예시

이렇게 해서 기존 명령형 기반 뷰 시스템에서 다이얼로그를 구성하는 방법부터 선언형 기반 compose 에서 다이얼로그를 구성하는 방법까지 살펴 보았습니다

해당 글에서 소개된 방법들을 통해 서비스 요구사항에 맞게 다양한 다이얼로그들을 구현하기 바랍니다

감사합니다

참고 문서

개인앱도 개발 중 입니다

  • 혹시 자기전에 핸드폰 보다가 늦게 주무시나요?
    잠글 시간 을 통해 스마트폰 중독에서 벗어나세요!!

--

--