Compose中ModalBottomSheet在API29及以下版本的imePadding问题
解决Compose ModalBottomSheet在API29及以下的键盘适配问题
问题描述
在API29及以下的Android系统中,Compose的ModalBottomSheet无法正确识别imePadding:键盘弹出时会遮挡底部内容,且不会触发重组;但API30及以上版本表现正常,键盘弹出会触发重组,Sheet可滚动显示全部内容。已在Manifest中为Activity添加adjustResize属性,该属性对普通页面有效,但对ModalBottomSheet无效。
解决方案
方案1:手动监听键盘高度,动态调整内边距
API29及以下系统不会自动通知Compose键盘高度变化,通过原生View的全局布局监听获取键盘高度,动态给Sheet内容添加底部内边距。
@OptIn(ExperimentalMaterial3Api::class) @Composable fun FilterScreen( component: SearchComponent, onDismiss: () -> Unit ) { val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) val countriesIS = remember { MutableInteractionSource() } val filterState by component.filterState.collectAsState() var showBottomSheet by remember { mutableStateOf(false) } val keyboardHeight = remember { mutableStateOf(0.dp) } val context = LocalContext.current // 监听键盘高度变化 DisposableEffect(Unit) { val rootView = (context as Activity).window.decorView.rootView val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { val displayFrame = Rect() rootView.getWindowVisibleDisplayFrame(displayFrame) val screenHeight = rootView.rootView.height val keyboardPx = screenHeight - displayFrame.bottom // 过滤误判(比如系统导航栏高度) keyboardHeight.value = if (keyboardPx > screenHeight * 0.15) { (keyboardPx / context.resources.displayMetrics.density).dp } else { 0.dp } } rootView.viewTreeObserver.addOnGlobalLayoutListener(layoutListener) onDispose { rootView.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) } } ModalBottomSheet( onDismissRequest = { showBottomSheet = false onDismiss() }, sheetState = sheetState, shape = RoundedCornerShape(topEnd = 50.dp, topStart = 50.dp), containerColor = Color.White ) { Column( modifier = Modifier .fillMaxHeight() .verticalScroll(rememberScrollState()) // 替换imePadding为动态计算的键盘高度 .padding(bottom = keyboardHeight.value), ) { AutocompleteTextEdit( labelText = Strings.getString("citizenship"), interactionSource = countriesIS, text = filterState.countriesString, items = component.countries, onValueChange = { newText -> component.onEvent(SearchEvents.UpdateCountries(newText)) }, ) Spacer(Modifier.requiredHeight(12.dp)) Row { DefaultButton( modifier = Modifier .weight(1f) .requiredHeight(mainButtonHeight) .padding(start = mainSideMargin, end = halfMainSideMargin), text = Strings.getString("clear"), isFilled = false, ) { component.onEvent(SearchEvents.ClearFilter) } DefaultButton( modifier = Modifier .weight(1f) .requiredHeight(mainButtonHeight) .padding(start = 8.dp, end = mainSideMargin), text = Strings.getString("search"), ) { component.onEvent(SearchEvents.ApplyFilter) } } Spacer(Modifier.requiredHeight(250.dp)) } } }
方案2:手动获取WindowInsets键盘参数
部分低版本Compose对imePadding的处理存在bug,可手动提取键盘Insets并应用:
@Composable private fun KeyboardPadding(): Modifier { val imeInsets = WindowInsets.ime.getInsets(LocalWindowInsets.current) val keyboardBottom = remember { mutableStateOf(0.dp) } LaunchedEffect(imeInsets) { keyboardBottom.value = imeInsets.bottom.toDp() } return Modifier.padding(bottom = keyboardBottom.value) } // 使用时替换原有的imePadding: Column( modifier = Modifier .fillMaxHeight() .verticalScroll(rememberScrollState()) .then(KeyboardPadding()), ) { // 原有内容不变 }
方案3:动态限制Sheet最大高度
结合屏幕高度和键盘高度,强制Sheet的最大高度为可用窗口高度,避免内容被遮挡:
@Composable fun FilterScreen( component: SearchComponent, onDismiss: () -> Unit ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val configuration = LocalConfiguration.current val screenHeight = remember(configuration) { configuration.screenHeightDp.dp } val keyboardHeight = remember { mutableStateOf(0.dp) } // 同方案1的键盘监听逻辑... ModalBottomSheet( onDismissRequest = { showBottomSheet = false; onDismiss() }, sheetState = sheetState, shape = RoundedCornerShape(topEnd = 50.dp, topStart = 50.dp), containerColor = Color.White, // 限制Sheet最大高度 modifier = Modifier.heightIn(max = screenHeight - keyboardHeight.value) ) { Column( modifier = Modifier .fillMaxHeight() .verticalScroll(rememberScrollState()), ) { // 原有内容不变 } } }
方案说明
方案1是最可靠的适配方式,直接通过原生View监听获取键盘高度,不受Compose版本或系统API限制;方案2、3可根据你的Compose版本尝试,若方案1生效优先选用。
内容的提问来源于stack exchange,提问作者Al Sh
相关产品推荐
相关产品推荐

