这个Bug只有在Dialog内容可滚动,且滚动内容较长的情况下会出现!
另外此Bug目前出现在 Jetpack Compose 1.5.3 及以下版本。
当前 Android 的 Jetpack Compose 版本是 1.5.3, 其 Dialog 存在一个Bug,无法动态调整大小,不会响应AdjustResize。
在键盘弹出时不会动态调整其大小,行为表现和 ajustPan 一致。Activity 设置 adjustResize 也不起作用。表现如下图所示:
相关代码如下,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 只解决了很小的一部分问题:
- 必须设置 Compose Dialog 属性
usePlatformDefaultWidth = true
才会起作用,但是我希望我的Dialog是宽度铺满屏幕的。 - 这个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 的行为导致的。
这个新的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内容的最大高度。