회사 업무를 진행하며 웹뷰와 소프트 키보드 관련 이슈가 발생하였습니다.
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
사이에 공백이 사라진 것을 확인할 수 있습니다.
글 속에 잘못된 정보가 있거나 궁금한 점이 있다면 언제든 댓글 부탁드립니다.
감사합니다.