👀

【超雑記】キーボードに対していい感じに動作するダイアログを作成する

に公開

普通にダイアログを使うと隠れてしまう・・・

こんな感じに動作させる

実装

コード

@Composable
fun GrowDialog(
    expanded: Boolean,
    onDismissRequest: () -> Unit,
    properties: GrowDialogProperties = GrowDialogProperties(),
    content: @Composable () -> Unit,
) {
    if (expanded) {
        Dialog(
            onDismissRequest = onDismissRequest,
            properties = properties.properties
        ) {
            Column(
                modifier = Modifier
                    .fillMaxHeight()
                    .then(
                        if (properties.dismissOnClickOutside) {
                            Modifier.clickable(
                                onClick = onDismissRequest,
                                indication = null,
                                interactionSource = remember { MutableInteractionSource() }
                            )
                        } else Modifier
                    ),
                verticalArrangement = Arrangement.Bottom,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                val configuration = LocalConfiguration.current
                val screenHeight = configuration.screenHeightDp.dp
                var bottomSpaceHeight by remember { mutableStateOf(screenHeight / 3) }

                val imeHeight = WindowInsets.ime.asPaddingValues().calculateBottomPadding()

                if (imeHeight > bottomSpaceHeight) { // imeHeight > screenHeight / 3 とすると高さは記憶されない
                    bottomSpaceHeight = imeHeight
                }
                Surface(
                    modifier = Modifier
                        .heightIn(max = screenHeight - bottomSpaceHeight)
                        .clickable(
                            interactionSource = remember { MutableInteractionSource() },
                            indication = null,
                            onClick = { /* do nothing */ },
                        ),
                    content = content
                )
                Spacer(modifier = Modifier.height(bottomSpaceHeight))
            }
        }
    }
} 

/*
 * : DialogProperties by DialogProperties(decorFitsSystemWindows: Boolean = false)
 */
class GrowDialogProperties(
    dismissOnBackPress: Boolean = true,
    dismissOnClickOutside: Boolean = true,
    securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
    usePlatformDefaultWidth: Boolean = true,
    decorFitsSystemWindows: Boolean = false
) {
    private val dialogProperties = DialogProperties(
        dismissOnBackPress = dismissOnBackPress,
        dismissOnClickOutside = dismissOnClickOutside,
        securePolicy = securePolicy,
        usePlatformDefaultWidth = usePlatformDefaultWidth,
        decorFitsSystemWindows = decorFitsSystemWindows
    )

    val properties: DialogProperties get() = dialogProperties

    val dismissOnBackPress: Boolean get() = dialogProperties.dismissOnBackPress
    val dismissOnClickOutside: Boolean get() = dialogProperties.dismissOnClickOutside
    val securePolicy: SecureFlagPolicy get() = dialogProperties.securePolicy
    val usePlatformDefaultWidth: Boolean get() = dialogProperties.usePlatformDefaultWidth
    val decorFitsSystemWindows: Boolean get() = dialogProperties.decorFitsSystemWindows
}

// 呼び出し側
@Composable
fun GrowDialogSample() {
    var shown by remember { mutableStateOf(true) }
    GrowDialog(
        expanded = shown,
        onDismissRequest = { shown = false },
    ) {
        var text by remember { mutableStateOf("") }
        OutlinedTextField(
            value = text,
            onValueChange = { text = it },
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp)
        )
    }

内容

  • 透明なダイアログを画面全体に広げ、キーボードに合わせてSpacerheightInによってレイアウトを制御
  • decorFitsSystemWindows: Boolean = falseとする必要があるため、DialogPropertiesをラップしたGrowDialogPropertiesを用意
  • その他、クリックイベントなどのあれこれを対策

おまけ

TextFieldの上下に他の要素を付けたい場合

weightで制約をかければ良い

@Composable
fun GrowDialogSample() {
    GrowDialog(
        expanded = shown,
        onDismissRequest = { shown = false }
    ) {
        Column(modifier = Modifier.fillMaxWidth()) {

            var text by remember { mutableStateOf("") }

            Text(
                text = "Keyboard Height Test",
                style = MaterialTheme.typography.bodyLarge,
                modifier = Modifier
                    .height(80.dp)
            )

            OutlinedTextField(
                value = text,
                onValueChange = {
                    text = it
                },
                placeholder = { Text("入力してください") },
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(weight = 1f, fill = false)
                    .padding(8.dp),
                minLines = 2,
                maxLines = Int.MAX_VALUE
            )

            TextButton(
                onClick = { shown = false },
                modifier = Modifier
                    .align(Alignment.End)
                    .background(MaterialTheme.colorScheme.background),
            ) {
                Text(text = "閉じる")
            }
        }
    }
}

キーボードに関係なく画面からはみ出なくしたい場合

screenHeightDpで制限をかければよい

@Composable
fun BasicDialog(
    expanded: Boolean,
    onDismissRequest: () -> Unit,
    properties: DialogProperties = DialogProperties(),
    content: @Composable () -> Unit,
) {
    if (expanded) {
        Dialog(
            onDismissRequest = onDismissRequest,
            properties = properties
        ) {
            val configuration = LocalConfiguration.current
            val screenHeight = configuration.screenHeightDp.dp

            Surface(
                modifier = Modifier
                    .heightIn(max = screenHeight)
                    .clickable(
                        interactionSource = remember { MutableInteractionSource() },
                        indication = null,
                        onClick = { /* do nothing */ },
                    ),
                content = content
            )
        }
    }
}

Discussion