这个Bug只有在Dialog内容可滚动,且滚动内容较长的情况下会出现!

另外此Bug目前出现在 Jetpack Compose 1.5.3 及以下版本。

当前 Android 的 Jetpack Compose 版本是 1.5.3, 其 Dialog 存在一个Bug,无法动态调整大小,不会响应AdjustResize。

在键盘弹出时不会动态调整其大小,行为表现和 ajustPan 一致。Activity 设置 adjustResize 也不起作用。表现如下图所示:

compose dialog adjust resize not work

相关代码如下,Activity 已启用 adjustResize:

@Composable
fun InputDialog(
    onCancel: () -> Unit,
    onConfirm: (String) -> Unit,
) {
    Dialog(
        onDismissRequest = onCancel,
        properties = DialogProperties(usePlatformDefaultWidth = false)
    ) {
        val window = (LocalView.current.parent as DialogWindowProvider).window
        window.setGravity(Gravity.BOTTOM)
        var value by remember { mutableStateOf("") }
        Surface(shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) {
            Column(modifier = Modifier) {
                Text(
                    text = "What's your name?",
                    style = MaterialTheme.typography.titleMedium,
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(56.dp)
                        .wrapContentSize()
                )
                Divider()
                Column(
                    modifier = Modifier
                        .weight(1f, false)
                        .verticalScroll(rememberScrollState())
                ) {
                    repeat(4) {
                        Text(
                            text = "Some Content", modifier = Modifier
                                .fillMaxWidth()
                                .padding(16.dp)
                        )
                    }
                    OutlinedTextField(
                        value = value,
                        onValueChange = { value = it },
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(16.dp)
                    )
                }
                Divider()
                Button(
                    onClick = { onConfirm(value) },
                    Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(text = "Submit")
                }
            }
        }
    }
}

Bug 原因追溯

官方其实已经解决过此Bug,具体看这个 issuetracker:Topmost Dialog view size is not updated after initial recomposition

但这个 Commit 只解决了很小的一部分问题:

  1. 必须设置 Compose Dialog 属性 usePlatformDefaultWidth = true 才会起作用,但是我希望我的Dialog是宽度铺满屏幕的。
  2. 这个Bug不是为了解决 AdjustResize 键盘高度问题的,所以对我们的使用场景没有任何帮助。

按现在流行的话来说就是:如解。

AdjustResize 为什么无效

因为从 Android 11 开始,WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 被标记为过时,键盘高度会通过 WindowInsets 分发 WindowInsets.Type.ime(),所以 Compose 的 Dialog 中就存在以下判断逻辑:

androidx/compose/ui/window/AndroidDialog.android.kt

fun updateParameters(
    onDismissRequest: () -> Unit,
    properties: DialogProperties,
    layoutDirection: LayoutDirection
) {
    this.onDismissRequest = onDismissRequest
    this.properties = properties
    setSecurePolicy(properties.securePolicy)
    setLayoutDirection(layoutDirection)
    // 这个if语句就是官方为了解决无法动态调整Dialog大小的Bug给加上的
    if (properties.usePlatformDefaultWidth && !dialogLayout.usePlatformDefaultWidth) {
        // Undo fixed size in internalOnLayout, which would suppress size changes when
        // usePlatformDefaultWidth is true.
        window?.setLayout(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT
        )
    }
    dialogLayout.usePlatformDefaultWidth = properties.usePlatformDefaultWidth

    // 这里就是适配 Android 11 WindowInsets 的逻辑判断
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
        @OptIn(ExperimentalComposeUiApi::class)
        if (properties.decorFitsSystemWindows) {
            window?.setSoftInputMode(defaultSoftInputMode)
        } else {
            @Suppress("DEPRECATION")
            window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
        }
    }
}

很明显从 Android 11 开始,Compose Dialog 直接弃用了 WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE ,所以这就是为什么 Dialog 无法响应键盘高度,那么我们就尝试使用 WindowInsets 解决问题。

尝试使用 WindowInsets 解决 (产生了另一个Bug)

Compose 已经提供了一个很方便的API来处理键盘弹出分发的 WindowInset,那就是使用 Modifier.imePadding(),在Dialog中使用这个API要设置 decorFitsSystemWindows = false,这个在Dialog的文档注释中已经很明确说明了,修改代码如下:

@Composable
fun InputDialog(
    onCancel: () -> Unit,
    onConfirm: (String) -> Unit,
) {
    Dialog(
        onDismissRequest = onCancel,
        properties = DialogProperties(
            usePlatformDefaultWidth = false
            decorFitsSystemWindows = false, // <= 修改了这里
        )
    ) {
        ...

        Surface(
            shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
            modifier = Modifier.imePadding(), // <= 修改了这里
        ) {
            ...
        }
    }
}

原理是在键盘弹出时,给我们的布局添加一个键盘高度的 bottomPadding,但是看看效果有点崩不住了,如果不滚动的时候是好的,但是尝试滚动到内容到顶部就出现了新的Bug。另外就是能看到 Dialog 内容区域有很明显的调整高度的行为,就是布局上下抖动了一下,这可能是 Recomposition 的行为导致的。

compose dialog adjust resize not work 2

这个新的Bug我已经不想再继续排查了,不管它是滚动的bug还是windowInsets的bug。其实我早就找到了一种不那么优雅,但是能解决AdjustResize问题的方案。

补充一下 Android 11 以下不使用 WindowInsets 的情况:
在Android 11 以下,虽然 Dialog 使用了 SOFT_INPUT_ADJUST_RESIZE,但是好像存在更严重的Bug,例如内容无法滚动,这里就不展开说明了。

解决方案

代码我就不贴了,查看 Stackoverflow 的这个回答即可:- Jetpack Compose - imePadding() for AlertDialog

原理是在 Activity 中放置一个全屏的不可见的 Box,监听它的高度变化,然后设置Dialog内容的最大高度。

References