Modifier.consumeWindowInsets 에 대하여

KEH·2024년 11월 11일
0

Compose

목록 보기
2/2
post-thumbnail

회사 업무를 진행하며 웹뷰와 소프트 키보드 관련 이슈가 발생하였습니다.

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()
            )
        }
    },
)


ScaffoldcontentWebView가 아닌 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>


하지만 ScaffoldcontentWebView가 적용되는 순간 키보드와 WebView 사이에 간격(빨간색 선)이 발생하게 됩니다.

저 간격의 정체가 대체 뭘까 알아보니 ScaffoldbottomBar 의 높이라는 것을 알 수 있었습니다.
(사진만으로는 믿을 수 없다는 분들 좀 뒤에 코드로도 증명하겠습니다.)

ScaffoldcontentWebView 가 아닌 다른 컴포넌트가 적용되었을 때는 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 의 패딩을 적용하게 합니다.

이번 예제에서는 AndroidViewmodifiercomsumeWindowInsets 을 사용하고, 이때 PaddingValues 에서 bottomPadding 으로 bottomBar 의 높이를 설정하면 "imePadding 을 적용할 때 bottomBar 의 높이를 제외하고 전달해줘." 라는 메세지를 전달할 수 있습니다.


그렇다면 한가지 의문이 더 발생하게 됩니다. 우리는 bottomBar 의 높이를 어떻게 구할 수 있을까요 ?

@Composable
fun Scaffold(
    ...
    
    content: @Composable (PaddingValues) -> Unit
)

Scaffoldcontent 파라미터에 전달 되는 PaddingValuesbottomPaddingbottomBar 의 높이가 됩니다.

content 에 전달 되는 PaddingValuesScaffold 에서 적용될 수 있는 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 로 설정하고 contentinnerPadding(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")
        	}
    	)
    },
)

AndroidViewmodifierconsumeWindowInsets(innerPadding) 을 적용하면 소프트 키보드와 WebView 사이에 공백이 사라진 것을 확인할 수 있습니다.


글 속에 잘못된 정보가 있거나 궁금한 점이 있다면 언제든 댓글 부탁드립니다.
감사합니다.

profile
:P

0개의 댓글