회사 업무를 진행하며 웹뷰와 소프트 키보드 관련 이슈가 발생하였습니다.
Scaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
bottomBar = {
Text(
text = "Bottom Navigation",
modifier = Modifier
.background(Color.Yellow)
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = 16.dp),
fontSize = 32.sp,
textAlign = TextAlign.Center,
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
verticalArrangement = Arrangement.Bottom
) {
TextField(
value = "",
onValueChange = {},
modifier = Modifier.fillMaxWidth()
)
}
},
)

Scaffold 의 content 로 WebView가 아닌 Column 내부에 TextField 를 적용했을 때는 키보드 위로 TextFiled 가 올라갑니다.
Scaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
bottomBar = {
Text(
text = "Bottom Navigation",
modifier = Modifier
.background(Color.Yellow)
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = 16.dp),
fontSize = 32.sp,
textAlign = TextAlign.Center,
)
},
content = { innerPadding ->
AndroidView(
factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
}
},
modifier = Modifier
.padding(innerPadding)
.imePadding(),
update = {
it.loadUrl("file:///android_asset/test.html")
}
)
},
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fixed Bottom Text Input</title>
<style>
/* 페이지 전체 스타일 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* 컨텐츠가 중앙에 위치하도록 설정 */
.content {
flex: 1;
padding: 20px;
}
/* 하단 입력 박스 스타일 */
.input-container {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #f1f1f1;
border-top: 1px solid #ccc;
padding: 10px;
display: flex;
align-items: center;
}
.input-container input[type="text"] {
flex: 1;
padding: 8px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
}
.input-container button {
margin-left: 10px;
padding: 8px 16px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.input-container button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<!-- 메인 콘텐츠 영역 -->
<div class="content">
<h1>Welcome to My Website</h1>
<p>This is an example page with a fixed text input at the bottom.</p>
<p>Scroll down to see the text input always at the bottom of the page.</p>
</div>
<!-- 하단 텍스트 입력 컴포넌트 -->
<div class="input-container">
<input type="text" placeholder="Type your message here..." />
<button type="button">Send</button>
</div>
</body>
</html>


하지만 Scaffold 의 content 로 WebView가 적용되는 순간 키보드와 WebView 사이에 간격(빨간색 선)이 발생하게 됩니다.
저 간격의 정체가 대체 뭘까 알아보니 Scaffold 의 bottomBar 의 높이라는 것을 알 수 있었습니다.
(사진만으로는 믿을 수 없다는 분들 좀 뒤에 코드로도 증명하겠습니다.)

Scaffold 의 content 로 WebView 가 아닌 다른 컴포넌트가 적용되었을 때는 Modifier.imePadding 을 사용하지 않고도 소프트 키보드가 올라오면 화면이 올라갑니다. 하지만 WebView 에서는 Modifier.imePadding 을 통해 소프트 키보드가 올라오면 자동으로 WebView 에 키보드 높이 만큼 패딩을 적용해야 합니다. 이 과정에서 bottomBar 의 높이가 고려되지 않은 것으로 예상됩니다.
원하는 대로 동작하기 위해서는 소프트 키보드가 올라올 때 (소프트 키보드 높이 - bottomBar의 높이) 로 패딩이 적용되어야 할텐데, 어떤 방법을 사용해야 할까요?
@Stable
fun Modifier.consumeWindowInsets(paddingValues: PaddingValues): Modifier = composed(
debugInspectorInfo {
name = "consumeWindowInsets"
properties["paddingValues"] = paddingValues
}
) {
remember(paddingValues) {
PaddingValuesConsumingModifier(paddingValues)
}
}
이때 우리는 Modifier.comsumeWindowInsets(PaddingValues) 속성을 사용합니다.
Modifier.comsumeWindowInsets(PaddingValues) 은 WindowInsets 에 해당되는 상태바(statusBar), 네비게이션바(navigationBar), 키보드(ime)의 패딩을 적용할 때 이미 PaddingValues 만큼은 적용되어 있으니 이 값을 제외하고 WindowInsets 의 패딩을 적용하게 합니다.
이번 예제에서는 AndroidView 의 modifier 에 comsumeWindowInsets 을 사용하고, 이때 PaddingValues 에서 bottomPadding 으로 bottomBar 의 높이를 설정하면 "imePadding 을 적용할 때 bottomBar 의 높이를 제외하고 전달해줘." 라는 메세지를 전달할 수 있습니다.
그렇다면 한가지 의문이 더 발생하게 됩니다. 우리는 bottomBar 의 높이를 어떻게 구할 수 있을까요 ?
@Composable
fun Scaffold(
...
content: @Composable (PaddingValues) -> Unit
)
Scaffold 의 content 파라미터에 전달 되는 PaddingValues 의 bottomPadding 이 bottomBar 의 높이가 됩니다.
content 에 전달 되는 PaddingValues 는 Scaffold 에서 적용될 수 있는 TopBar, BottomBar 차지하는 공간을 고려하여 content 의 여백이 적절하게 설정되도록 하는 padding 입니다. 따라서 bottomPadding 에는 BottomBar 의 높이가 들어가 있겠지요 ?
Scaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
bottomBar = {
Text(
text = "Bottom Navigation",
modifier = Modifier
.background(Color.Yellow)
.fillMaxWidth()
.height(120.dp),
fontSize = 32.sp,
textAlign = TextAlign.Center,
)
},
content = { innerPadding ->
Log.d("[TEST] KEH", "innerPadding: $innerPadding")
AndroidView(
factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
}
},
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding(),
update = {
it.loadUrl("file:///android_asset/test.html")
}
)
},
)
bottomBar 의 높이를 120dp 로 설정하고 content 의 innerPadding(PaddingValues) 로그를 출력하면 bottomPadding 이 120dp 임을 알 수 있습니다.
2024-11-11 23:43:58.741 7869-7869 [TEST] KEH com.study.consumewindowinsets D innerPadding: PaddingValues(start=0.0.dp, top=0.0.dp, end=0.0.dp, bottom=120.0.dp)
Scaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
bottomBar = {
Text(
text = "Bottom Navigation",
modifier = Modifier
.background(Color.Yellow)
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = 16.dp),
fontSize = 32.sp,
textAlign = TextAlign.Center,
)
},
content = { innerPadding ->
AndroidView(
factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
}
},
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding(),
update = {
it.loadUrl("file:///android_asset/test.html")
}
)
},
)
AndroidView 의 modifier 에 consumeWindowInsets(innerPadding) 을 적용하면 소프트 키보드와 WebView 사이에 공백이 사라진 것을 확인할 수 있습니다.

글 속에 잘못된 정보가 있거나 궁금한 점이 있다면 언제든 댓글 부탁드립니다.
감사합니다.
감사합니다! 덕분에 문제를 해결할 수 있었습니다~