Developing Myself Everyday
article thumbnail
Published 2024. 4. 23. 19:18
Compose의 WindowInsets Android/Compose

사진: UnsplashR Mo

 

 

 

이번 게시글에서는 Compose에서 어떻게 WindowInsets에 대응하는지 아래의 공식문서로 공부해보고자 합니다.

 

Compose의 창 인셋  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose의 창 인셋 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 플랫폼은 상태 표시줄 및 탐색

developer.android.com


 

 

 

 

WindowInsets?


`WindowInsets`에 대해서 잘 알지 못하는 분들을 위해 먼저 설명해 보겠습니다.

 

WindowInsets은 `Window`와 `Insets`으로 나눠 설명하면 매우 쉽게 이해할 수 있습니다. 


`Window`는 일반적으로 앱이나 액티비티가 실행되면 화면 공간으로 사용자에게 앱의 내용을 표시하는 화면입니다.
`Insets`은 Window의 상단, 하단, 좌측, 우측에 위치한 시스템의 UI 요소와 관련된 여백을 나타내기 위해 사용되는 여백입니다.

 

즉 WindowInsets은 시스템 UI에 대한 요소를 화면에 반영할 때, 알맞은 인셋을 통해 사용자에게 좋은 경험을 제공하는 요소입니다.

 

 

 

 

기본으로 적용되어 있는 내장 Insets


 

안드로이드의 앱에서는 위와 같이 `Status bar`와 `Navigation bar`가 존재합니다. 이러한 UI 요소들과 화면이 겹치지 않기 위해서 WindowInsets이 적용되어 사용자 경험을 향상합니다.

 

안드로이드에서는 이외에도 다양한 내장 인셋들이 있고 이는 WindowInsets 인터페이스를 구현하고 있습니다.

@Stable
interface WindowInsets {

    fun getLeft(density: Density, layoutDirection: LayoutDirection): Int

    fun getTop(density: Density): Int

    fun getRight(density: Density, layoutDirection: LayoutDirection): Int

    fun getBottom(density: Density): Int
    
    companion object
}

 

 

아래의 패키지로 가보면 WindowInsets 인터페이스를 구현하는 다양한 구현체들이 존재합니다.

 

androidx.compose.foundation.layout

 

 

구현체중 하나인 `statusBars`를 한번 보겠습니다. 

val WindowInsets.Companion.statusBars: WindowInsets
    @Composable
    @NonRestartableComposable
    get() = WindowInsetsHolder.current().statusBars

 

 

statusBars를 가져오는 곳은 바로 `WindowInsetsHolder`인데 이는 현재 화면의 인셋 정보를 알려주는 Class로 아래와 같이 다양한 정보를 제공합니다.

internal class WindowInsetsHolder private constructor(insets: WindowInsetsCompat?, view: View) {
    val captionBar =
        systemInsets(insets, WindowInsetsCompat.Type.captionBar(), "captionBar")
    val displayCutout =
        systemInsets(insets, WindowInsetsCompat.Type.displayCutout(), "displayCutout")
    val ime = systemInsets(insets, WindowInsetsCompat.Type.ime(), "ime")
    val mandatorySystemGestures = systemInsets(
        insets,
        WindowInsetsCompat.Type.mandatorySystemGestures(),
        "mandatorySystemGestures"
    )
    val navigationBars =
        systemInsets(insets, WindowInsetsCompat.Type.navigationBars(), "navigationBars")
    val statusBars =
        systemInsets(insets, WindowInsetsCompat.Type.statusBars(), "statusBars")
    val systemBars =
        systemInsets(insets, WindowInsetsCompat.Type.systemBars(), "systemBars")
        
        ...
}

 

 

WindowInsetsHolder는 `WindowInsetsCompat`으로부터 화면의 여백 정보를 가져옵니다. 이렇게 다양한 인셋 정보를 통해 인셋 처리를 할 수 있습니다.

 

 

 

 

인셋을 앱의 콘텐츠에 적용하는 방법


기본적으로 우리가 구현하는 View들은 시스템 UI 아래에 위치하게 됩니다. 다름의 예시를 보면 쉽게 이해가 됩니다.

 

아래의 화면은 아무것도 적용하지 않은 상태의 화면입니다. 상태 표시줄 아래에 Text가 위치합니다.

 

 

 

 

다음은 View들을 시스템 UI 아래에 위치시킨 상태의 화면입니다. 확연한 차이를 보실 수 있습니다.

 

 

 

 

 

원하는 대로 인셋을 제어하고 적용하기 위해서는 바로 위의 그림과 같이 View들을 시스템 UI의 아래에 위치시켜야 합니다. 이를 통해 View들과 시스템 UI 사이의 여백을 조절하거나 앱 콘텐츠가 시스템 UI에 가려지는 부분을 처리할 수 있습니다.

 

 

안드로이드에서 View들을 시스템 UI의 아래에 위치시킬 수 있는 방법은 여러 가지가 있습니다. 먼저 첫 번째 방법은 아래와 같이 `setDecorFitsSystemWindows`를 `false`로 설정해야 합니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, false)

    setContent {
        PracticeTheme {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

 

`Decor`는 상태 표시줄이나 내비게이션 바와 같은 시스템 UI 요소를 말하는 데코레이션 뷰입니다.

 

WindowCompat.setDecorFitsSystemWindows(window, false)false로 설정하면, 데코레이션 뷰가 시스템 UI에 맞추어지지 않고, WindowInsetsCompat을 앱의 콘텐츠 뷰로 직접 전달합니다. 이렇게 하면 앱의 뷰가 시스템 UI 위에 위치하게 됩니다.

 

 

또 하나의 방법은 `enableEdgeToEdge`를 적용하는 방법입니다. 해당 방법은 앱을 더 넓은 화면으로 만들기 위한 방법으로 동일한 역할을 합니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    
    setContent {
        PracticeTheme {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

 

 

 

 

Insets을 적용해보기


인셋을 사용하는 방법은 너무나도 많기에 여기서는 간단히 몇 가지만 알아보겠습니다.

 

아래와 같이 키보드를 사용하는 경우에 앱의 콘텐츠가 가려지는 경우가 있습니다. 

 

 

이러한 경우에 `imePadding`을 사용하면 매우 간단하게 ime에 대한 패딩을 적용할 수 있습니다.

ime?
IME는 'Input Method Editor'의 약자로, 사용자가 텍스트를 입력할 때 키보드와 같은 입력 도구를 제공하는 소프트웨어입니다. 키보드 외에도 음성 입력, 터치 입력 등 다양한 입력 방식을 지원합니다.
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box(
        Modifier.imePadding()
    ) {
        Text(text = "테스트", fontSize = 40.sp, modifier = Modifier.align(Alignment.BottomCenter))
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

 

 

매우 간단하게 이제 키보드에 대한 인셋을 적용할 수 있습니다.

 

 

 

 

마무리하며


이렇게 해서 Compose의 WindowInsets에 대해 조금이지만 알아보는 시간을 가졌습니다. 다루지 않은 부분이 엄청 많고 인셋으로 할 수 있는 것들은 무궁무진합니다. 

 

이 게시글로 인해서 나중에 인셋을 적용해야 할 일이 있을 때, 두려워하지 않고 적용할 수 있었으면 좋겠습니다. 

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!