JetpackCompose의 AlertDialog에서 시간을 녹였다.

JetpackCompose에서 AlertDialog를 사용할 때 실패한 이야기입니다.

정리하면


  • AlertDialog를 사용할 때 Text만으로 간단한 구성으로 설정하는 것은 무난합니다.
  • TextField를 넣고 싶을 때는 Dialog를 사용합시다
  • 원래 대화가 필요한지 생각합시다

  • 무슨 실패?



    텍스트 입력 대화 상자를 AlertDialog를 사용하여 만들면 레이아웃이 무너질 수 있습니다.


    @Composable
    fun MyDialog4(onDismiss: () -> Unit) {
        val (text, setText) = remember { mutableStateOf("") }
        AlertDialog(
            onDismissRequest = onDismiss,
            text = {
                TextField(
                    value = text,
                    onValueChange = setText,
                    modifier = Modifier.padding(32.dp),
                )
            },
            confirmButton = { ... },
        )
    }
    

    환경


  • Android Studio: Arctic Fox 2020.3.1 RC 1
  • Kotlin: 1.5.10
  • androidx.compose.*: 1.0.0-rc02

  • 왜 일어났어?



    이 레이아웃 붕괴가 발생한 원인을 탐구하기 위해 AlertDialog의 내용을 살펴 보겠습니다.
    그러면 ColumnScope.AlertDialogBaselineLayout에 도착합니다.
    여기에서는 받은 제목과 텍스트를 배치합니다.
    @Composable
    internal fun ColumnScope.AlertDialogBaselineLayout(
        title: @Composable (() -> Unit)?,
        text: @Composable (() -> Unit)?
    ) {
        Layout(
            {
                title?.let { title ->
                    Box(...) {
                        title()
                    }
                }
                text?.let { text ->
                    Box(...) {
                        text()
                    }
                }
            },
            Modifier.weight(1f, false)
        ) { measurables, constraints ->
            val titlePlaceable = measurables.firstOrNull { it.layoutId == "title" }?.measure(
                constraints.copy(minHeight = 0)
            )
            val textPlaceable = measurables.firstOrNull { it.layoutId == "text" }?.measure(
                constraints.copy(minHeight = 0)
            )
    
            // このあたりでタイトルとテキストの位置を計算
            ...
    
            layout(layoutWidth, layoutHeight) {
                titlePlaceable?.place(0, titlePositionY)
                textPlaceable?.place(0, textPositionY)
            }
        }
    }
    

    보다 자세하게 살펴보면, 타이틀·텍스트 각각에 대해, 베이스 라인을 보면서 세로 방향의 위치 결정을 하고 있습니다(가로 방향은 단순하기 때문에 본고에서는 할애)
    세세한 내용은 실제 코드를 읽을 수 있다고 생각하지만, 여기서 중요한 점은 기준선의 위치가 특정 위치에 오도록 배치하고 있다는 것입니다. (특정 위치는 sp 의존의 고정값)
    // Place the title so that its first baseline is titleOffset from the top
    val titlePositionY = titleOffset - firstTitleBaseline
    

    이것을 모르면 몇 가지 문제가 발생할 수 있습니다.

    top 방향의 padding이 효과가 없다



    기준선의 위치를 ​​특정 위치에 맞추려고하기 때문에 top에 padding을 지정해도 효과가 없습니다.
    또한 큰 값을 넣으면 텍스트 자체가 무너집니다.


    pad =
    0
    16
    28







    @Composable
    fun MyDialog5(onDismiss: () -> Unit) {
        val pad = // 表中の値
        AlertDialog(
            onDismissRequest = onDismiss,
            title = null,
            text = {
                Text(text = "MyDialog5", modifier = Modifier.padding(top = pad.dp))
            },
            confirmButton = { CancelButton(onDismiss) },
        )
    }
    

    Text와 TextField에서 차이가 발생합니다.


    TextTextField 에서는 상단에서 기준선까지의 거리가 다릅니다.
    따라서 단순히 배치하는 것만으로도 어긋남이 발생합니다.


    텍스트
    TextField





    @Composable
    fun MyDialog2(onDismiss: () -> Unit) {
        AlertDialog(
            onDismissRequest = onDismiss,
            title = null,
            text = { Text(text = "MyDialog2") },
            confirmButton = { CancelButton(onDismiss) },
        )
    }
    
    @Composable
    fun MyDialog3(onDismiss: () -> Unit) {
        val (text, setText) = remember { mutableStateOf("") }
        AlertDialog(
            onDismissRequest = onDismiss,
            text = { TextField(value = text, onValueChange = setText) },
            confirmButton = { CancelButton(onDismiss) },
        )
    }
    

    해결안



    해결책으로는 다음과 같은 예를 생각할 수 있습니다.

    Dialog 사용



    직접 Dialog에서 구현합니다.
    방금 실제로 AlertDialog의 내용을 본 사람은 알 수 있다고 생각합니다.
    제목 등을 배치하는 경우, AlertDialog 로 설정되어 있던 alpha 나 textStyle 에 대해서도 스스로 설정할 필요가 있는 것에 주의합니다


    @Composable
    fun MyDialog6(onDismiss: () -> Unit) {
        val (text, setText) = remember { mutableStateOf("") }
        Dialog(onDismissRequest = onDismiss) {
            Surface {
                Column {
                    TextField(
                        value = text,
                        onValueChange = setText,
                        modifier = Modifier.padding(32.dp),
                    )
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(all = 8.dp),
                    ) {
                        CancelButton(
                            onClick = onDismiss,
                            modifier = Modifier
                                .align(Alignment.BottomEnd),
                        )
                    }
                }
            }
        }
    }
    

    대화상자 사용 안함



    해결책이라고는 말할 수 없을지도 모르지만, 처음에는 대화가 필요합니까?
    복잡한 UI가 필요하다면 대화의 생각에서 벗어나 일반 화면에 배치하는 것도 손 중 하나입니다.
    또, 다이얼로그는 유저의 조작을 블록하는 UI 이며, 그 이용에는 신중하게 할 필요가 있습니다

    참고 링크



    공식 문서
    머티리얼 디자인 가이드라인
    이번에 작성한 샘플 프로젝트, 대화 상자는 여기

    좋은 웹페이지 즐겨찾기