Android 플랫폼은 상태 표시줄 및 탐색 메뉴와 같은 시스템 UI를 그리는 역할을 합니다. 이 시스템 UI는 사용자가 사용하는 앱과 관계없이 표시됩니다.
WindowInsets
는 앱이 올바른 영역에 그려지고 UI가 시스템 UI에 가려지지 않도록 시스템 UI에 관한 정보를 제공합니다.
Android 14 (API 수준 34) 이하에서는 앱의 UI가 기본적으로 시스템 표시줄과 디스플레이 컷아웃 아래에 표시되지 않습니다.
Android 15 (API 수준 35) 이상에서는 앱이 SDK 35를 타겟팅하면 앱이 시스템 표시줄 아래에 그려지고 잘림 부분이 표시됩니다. 이렇게 하면 더 원활한 사용자 환경을 제공하고 앱에서 사용 가능한 창 공간을 최대한 활용할 수 있습니다.
시스템 UI 뒤에 콘텐츠를 표시하는 것을 더 넓은 화면을 활용한다고 합니다. 이 페이지에서는 다양한 유형의 인셋, 전체 화면으로 전환하는 방법, 인셋 API를 사용하여 UI에 애니메이션을 적용하고 앱 콘텐츠가 시스템 UI 요소로 가려지지 않도록 하는 방법을 알아봅니다.
인셋 기본사항
앱이 더 넓은 화면에 표시되면 중요한 콘텐츠와 상호작용이 시스템 UI에 가려지지 않도록 해야 합니다. 예를 들어 버튼이 탐색 메뉴 뒤에 배치된 경우 사용자가 버튼을 클릭하지 못할 수 있습니다.
시스템 UI의 크기와 배치 위치에 관한 정보는 인셋을 통해 지정됩니다.
시스템 UI의 각 부분에는 크기와 배치 위치를 나타내는 상응하는 유형의 인셋이 있습니다. 예를 들어 상태 표시줄 인셋은 상태 표시줄의 크기와 위치를 제공하는 반면 탐색 메뉴 인셋은 탐색 메뉴의 크기와 위치를 제공합니다. 각 유형의 인셋은 상단, 왼쪽, 오른쪽, 하단의 4가지 픽셀 크기로 구성됩니다. 이 크기는 시스템 UI가 앱 창의 해당 측면에서 얼마나 확장되는지를 지정합니다. 따라서 이러한 유형의 시스템 UI와 겹치지 않도록 하려면 앱 UI를 해당 크기만큼 안쪽으로 넣어야 합니다.
다음과 같은 기본 제공 Android 인셋 유형은 WindowInsets
를 통해 사용할 수 있습니다.
상태 표시줄을 설명하는 인셋입니다. 알림 아이콘과 기타 표시기가 포함된 상단 시스템 UI 표시줄입니다. |
|
상태 표시줄이 표시될 때의 인셋입니다. 몰입형 전체 화면 모드로 전환하여 현재 상태 표시줄이 숨겨져 있는 경우 기본 상태 표시줄 인셋은 비어 있지만 이러한 인셋은 비어 있지 않습니다. |
|
탐색 메뉴를 설명하는 인셋 기기의 왼쪽, 오른쪽 또는 하단에 있는 시스템 UI 막대로, 태스크 바 또는 탐색 아이콘을 설명합니다. 이는 사용자의 선호 탐색 방법과 작업 표시줄과의 상호작용에 따라 런타임에 변경될 수 있습니다. |
|
탐색 메뉴가 표시될 때의 탐색 메뉴 인셋 몰입형 전체 화면 모드로 전환되어 탐색 메뉴가 현재 숨겨져 있는 경우 기본 탐색 메뉴 인셋은 비어 있지만 이러한 인셋은 비어 있지 않습니다. |
|
상단 제목 표시줄과 같이 자유 형식 창에 있는 경우 시스템 UI 창 장식을 설명하는 인셋입니다. |
|
자막이 표시될 때 자막 표시줄이 안으로 들어가도록 설정합니다. 자막 표시줄이 현재 숨겨져 있으면 기본 자막 표시줄 인셋은 비어 있지만 이러한 인셋은 비어 있지 않습니다. |
|
상태 표시줄, 탐색 메뉴, 자막 표시줄을 포함한 시스템 표시줄 인셋의 합집합입니다. |
|
시스템 표시줄이 표시될 때의 인셋입니다. 몰입형 전체 화면 모드로 전환하여 현재 시스템 바가 숨겨져 있으면 기본 시스템 바 인셋이 비어 있지만 이러한 인셋은 비어 있지 않습니다. |
|
소프트웨어 키보드가 차지하는 하단의 공간 크기를 나타내는 인셋입니다. |
|
현재 키보드 애니메이션 전에 소프트웨어 키보드가 차지한 공간의 양을 나타내는 인셋입니다. |
|
현재 키보드 애니메이션 후 소프트웨어 키보드가 차지할 공간의 양을 나타내는 인셋입니다. |
|
내비게이션 UI에 관한 더 자세한 정보를 설명하는 인셋 유형으로, '탭'이 앱이 아닌 시스템에서 처리되는 공간의 양을 제공합니다. 동작 내비게이션이 있는 투명한 내비게이션 바의 경우 일부 앱 요소는 시스템 내비게이션 UI를 통해 탭할 수 있습니다. |
|
탭 가능한 요소가 표시될 때의 인셋입니다. 탭 가능한 요소가 현재 숨겨져 있는 경우 (몰입형 전체 화면 모드로 전환되어서) 기본 탭 가능한 요소 인셋은 비어 있지만 이러한 인셋은 비어 있지 않습니다. |
|
시스템이 탐색을 위해 동작을 가로채는 삽입의 양을 나타내는 삽입입니다. 앱은 |
|
항상 시스템에서 처리하며 |
|
디스플레이 컷아웃 (노치 또는 핀홀)과 겹치는 것을 방지하는 데 필요한 간격을 나타내는 인셋입니다. |
|
폭포식 디스플레이의 곡선 영역을 나타내는 인셋입니다. 워터폴 디스플레이는 화면이 기기 측면을 따라 래핑되기 시작하는 화면 가장자리에 곡선이 있는 영역이 있습니다. |
이러한 유형은 콘텐츠가 가려지지 않도록 하는 세 가지 '안전한' 인셋 유형으로 요약됩니다.
이러한 '안전한' 인셋 유형은 기본 플랫폼 인셋에 따라 다양한 방식으로 콘텐츠를 보호합니다.
WindowInsets.safeDrawing
를 사용하여 시스템 UI 아래에 그려져서는 안 되는 콘텐츠를 보호합니다. 이는 삽입의 가장 일반적인 용도입니다. 시스템 UI로 부분적으로 또는 완전히 가려진 콘텐츠를 그리지 않도록 하는 것입니다.WindowInsets.safeGestures
를 사용하여 동작으로 콘텐츠를 보호합니다. 이렇게 하면 시스템 동작이 앱 동작 (예: 하단 시트, 캐러셀 또는 게임의 동작)과 충돌하는 것을 방지할 수 있습니다.WindowInsets.safeContent
를WindowInsets.safeDrawing
및WindowInsets.safeGestures
의 조합으로 사용하여 콘텐츠에 시각적 중복이나 동작 중복이 없도록 합니다.
인셋 설정
앱에서 콘텐츠를 그리는 위치를 완전히 제어할 수 있도록 하려면 다음 설정 단계를 따르세요. 이러한 단계를 수행하지 않으면 앱이 시스템 UI 뒤에 검은색 또는 단색을 그리거나 소프트웨어 키보드와 동시에 애니메이션되지 않을 수 있습니다.
- Android 15 이상에서 가득 채우기를 적용하려면 SDK 35 이상을 타겟팅합니다. 앱이 시스템 UI 뒤에 표시됩니다. 인셋을 처리하여 앱의 UI를 조정할 수 있습니다.
- 원하는 경우
Activity.onCreate()
에서enableEdgeToEdge()
를 호출하여 이전 Android 버전에서 앱이 더 넓은 화면을 사용할 수 있도록 합니다. 활동의
AndroidManifest.xml
항목에서android:windowSoftInputMode="adjustResize"
를 설정합니다. 이 설정을 사용하면 앱에서 소프트웨어 IME의 크기를 인셋으로 수신할 수 있습니다. 이 인셋은 IME가 앱에 표시되고 사라질 때 콘텐츠를 적절하게 패딩하고 배치하는 데 사용할 수 있습니다.<!-- in your AndroidManifest.xml file: --> <activity android:name=".ui.MainActivity" android:label="@string/app_name" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.MyApplication" android:exported="true">
Compose API
활동에서 모든 인셋 처리를 제어하면 Compose API를 사용하여 콘텐츠가 가려지지 않고 상호작용할 수 있는 요소가 시스템 UI와 겹치지 않도록 할 수 있습니다. 또한 이러한 API는 앱의 레이아웃을 인셋 변경사항과 동기화합니다.
예를 들어 다음은 전체 앱의 콘텐츠에 인셋을 적용하는 가장 기본적인 방법입니다.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { Box(Modifier.safeDrawingPadding()) { // the rest of the app } } }
이 스니펫은 safeDrawing
창 인셋을 앱의 전체 콘텐츠 주위에 패딩으로 적용합니다. 이렇게 하면 상호작용할 수 있는 요소가 시스템 UI와 겹치지 않지만, 앱이 시스템 UI 뒤에 그려져 화면 전체에 효과를 주지 못합니다. 전체 창을 최대한 활용하려면 화면별 또는 구성요소별로 인셋이 적용되는 위치를 미세 조정해야 합니다.
이러한 모든 인셋 유형은 API 21로 백포팅된 IME 애니메이션으로 자동으로 애니메이션 처리됩니다. 또한 이러한 인셋을 사용하는 모든 레이아웃은 인셋 값이 변경될 때 자동으로 애니메이션됩니다.
이러한 인셋 유형을 사용하여 컴포저블 레이아웃을 조정하는 방법에는 패딩 수정자와 인셋 크기 수정자라는 두 가지 기본 방법이 있습니다.
패딩 수정자
Modifier.windowInsetsPadding(windowInsets: WindowInsets)
는 주어진 창 인셋을 패딩으로 적용하여 Modifier.padding
와 마찬가지로 작동합니다.
예를 들어 Modifier.windowInsetsPadding(WindowInsets.safeDrawing)
는 안전한 그리기 인셋을 4면 모두에 패딩으로 적용합니다.
가장 일반적인 인셋 유형을 위한 몇 가지 내장 유틸리티 메서드도 있습니다.
Modifier.safeDrawingPadding()
는 Modifier.windowInsetsPadding(WindowInsets.safeDrawing)
와 동일한 이러한 메서드 중 하나입니다. 다른 인셋 유형에도 유사한 수정자가 있습니다.
인셋 크기 수정자
다음 수정자는 구성요소의 크기를 인셋의 크기로 설정하여 창 인셋의 크기를 적용합니다.
windowInsets의 시작 면을 너비로 적용합니다 ( |
|
windowInsets의 끝 쪽을 너비로 적용합니다 ( |
|
windowInsets의 상단을 높이로 적용합니다 ( |
|
|
windowInsets의 하단을 높이로 적용합니다 ( |
이러한 수정자는 특히 인셋 공간을 차지하는 Spacer
의 크기를 조절하는 데 유용합니다.
LazyColumn( Modifier.imePadding() ) { // Other content item { Spacer( Modifier.windowInsetsBottomHeight( WindowInsets.systemBars ) ) } }
인셋 소비
인셋 패딩 수정자 (windowInsetsPadding
및 safeDrawingPadding
와 같은 도우미)는 패딩으로 적용되는 인셋의 일부를 자동으로 사용합니다. 컴포지션 트리를 더 깊이 들어가면서 중첩된 인셋 패딩 수정자와 인셋 크기 수정자는 인셋의 일부가 이미 외부 인셋 패딩 수정자에 의해 사용되었음을 알고 있으므로 인셋의 동일한 부분을 두 번 이상 사용하지 않아 과도한 여백이 발생하지 않도록 합니다.
인셋 크기 수정자는 인셋이 이미 사용된 경우 인셋의 동일한 부분을 두 번 이상 사용하지 않도록 합니다. 하지만 크기를 직접 변경하므로 자체적으로 인셋을 사용하지 않습니다.
따라서 패딩 수정자를 중첩하면 각 컴포저블에 적용되는 패딩의 양이 자동으로 변경됩니다.
이전과 동일한 LazyColumn
예시를 보면 LazyColumn
가 imePadding
수정자를 사용하여 크기가 조절됩니다. LazyColumn
내에서 마지막 항목은 시스템 표시줄 하단의 높이로 크기가 조정됩니다.
LazyColumn( Modifier.imePadding() ) { // Other content item { Spacer( Modifier.windowInsetsBottomHeight( WindowInsets.systemBars ) ) } }
IME가 닫혀 있으면 IME에 높이가 없으므로 imePadding()
수정자는 패딩을 적용하지 않습니다. imePadding()
수정자는 패딩을 적용하지 않으므로 인셋이 사용되지 않으며 Spacer
의 높이는 시스템 표시줄 하단의 크기가 됩니다.
IME가 열리면 IME 인셋이 IME 크기에 맞게 애니메이션을 적용하고 imePadding()
수정자는 IME가 열리면서 하단 패딩을 적용하여 LazyColumn
크기를 조절하기 시작합니다. imePadding()
수정자가 하단 패딩을 적용하기 시작하면 그 양만큼의 인셋도 사용하기 시작합니다. 따라서 시스템 표시줄의 간격 중 일부가 이미 imePadding()
수정자에 의해 적용되었으므로 Spacer
의 높이가 감소하기 시작합니다. imePadding()
수정자가 시스템 표시줄보다 큰 하단 패딩을 적용하면 Spacer
의 높이는 0입니다.
IME가 닫히면 변경사항이 역순으로 적용됩니다. imePadding()
가 시스템 표시줄의 하단보다 적게 적용되면 Spacer
가 0의 높이에서 확장되기 시작하여 IME가 완전히 애니메이션으로 나감에 따라 Spacer
가 시스템 표시줄의 하단 높이와 일치하게 됩니다.
이 동작은 모든 windowInsetsPadding
수정자 간의 통신을 통해 이루어지며 다른 몇 가지 방법으로 영향을 받을 수 있습니다.
Modifier.consumeWindowInsets(insets: WindowInsets)
도 Modifier.windowInsetsPadding
와 동일한 방식으로 인셋을 사용하지만 사용된 인셋을 패딩으로 적용하지는 않습니다. 이는 인셋 크기 수정자와 함께 사용하여 특정 크기의 인셋이 이미 사용되었음을 상위 요소에 알리는 데 유용합니다.
Column(Modifier.verticalScroll(rememberScrollState())) { Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars)) Column( Modifier.consumeWindowInsets( WindowInsets.systemBars.only(WindowInsetsSides.Vertical) ) ) { // content Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) } Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)) }
Modifier.consumeWindowInsets(paddingValues: PaddingValues)
는 WindowInsets
인수가 있는 버전과 매우 유사하게 동작하지만 임의의 PaddingValues
를 사용합니다. 이는 일반 Modifier.padding
또는 고정 높이 스페이서와 같이 인셋 패딩 수정자 이외의 다른 메커니즘에서 패딩이나 간격을 제공하는 경우 자식에게 알리는 데 유용합니다.
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) { // content Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) }
소비 없이 원시 창 인셋이 필요한 경우 WindowInsets
값을 직접 사용하거나 WindowInsets.asPaddingValues()
를 사용하여 소비의 영향을 받지 않는 인셋의 PaddingValues
를 반환합니다.
하지만 아래의 주의사항으로 인해 가능하면 창 인셋 패딩 수정자와 창 인셋 크기 수정자를 사용하는 것이 좋습니다.
인셋 및 Jetpack Compose 단계
Compose는 기본 AndroidX 핵심 API를 사용하여 인셋을 업데이트하고 애니메이션 처리합니다. 이때 인셋을 관리하는 기본 플랫폼 API가 사용됩니다. 이러한 플랫폼 동작으로 인해 인셋은 Jetpack Compose의 단계와 특별한 관계가 있습니다.
insets 값은 컴포지션 단계 후 레이아웃 단계 전에 업데이트됩니다. 즉, 컴포지션에서 인셋 값을 읽을 때는 일반적으로 1프레임 지연된 인셋 값이 사용됩니다. 이 페이지에 설명된 내장 수정자는 레이아웃 단계까지 인셋 값 사용을 지연하도록 빌드되므로 인셋 값이 업데이트될 때 동일한 프레임에서 사용됩니다.
WindowInsets
를 사용한 키보드 IME 애니메이션
스크롤 컨테이너에 Modifier.imeNestedScroll()
를 적용하여 컨테이너 하단으로 스크롤할 때 IME를 자동으로 열고 닫을 수 있습니다.
class WindowInsetsExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContent { MaterialTheme { MyScreen() } } } } @OptIn(ExperimentalLayoutApi::class) @Composable fun MyScreen() { Box { 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") } } }
Material 3 구성요소의 인셋 지원
사용 편의를 위해 대부분의 내장 Material 3 컴포저블(androidx.compose.material3
)은 Material 사양에 따라 앱에 컴포저블이 배치되는 방식에 따라 인셋을 직접 처리합니다.
인셋 처리 컴포저블
다음은 인셋을 자동으로 처리하는 Material 구성요소 목록입니다.
앱 바
TopAppBar
/SmallTopAppBar
/CenterAlignedTopAppBar
/MediumTopAppBar
/LargeTopAppBar
: 창 상단에 사용되므로 시스템 막대의 상단 및 가로 측면을 패딩으로 적용합니다.BottomAppBar
: 시스템 표시줄의 하단 및 가로 측면을 패딩으로 적용합니다.
콘텐츠 컨테이너
ModalDrawerSheet
/DismissibleDrawerSheet
/PermanentDrawerSheet
(모달 탐색 창 내 콘텐츠): 콘텐츠에 세로 및 시작 인셋을 적용합니다.ModalBottomSheet
: 하단 인셋을 적용합니다.NavigationBar
: bottom 및 horizontal 인셋을 적용합니다.NavigationRail
: 세로 및 시작 인셋을 적용합니다.
Scaffold
기본적으로 Scaffold
는 개발자가 사용하고 소비할 수 있도록 매개변수 paddingValues
로 인셋을 제공합니다.
Scaffold
는 콘텐츠에 인셋을 적용하지 않습니다. 이 책임은 개발자에게 있습니다.
예를 들어 Scaffold
내부의 LazyColumn
로 이러한 인셋을 사용하려면 다음을 실행합니다.
Scaffold { innerPadding -> // innerPadding contains inset information for you to use and apply LazyColumn( // consume insets as scaffold doesn't do it by default modifier = Modifier.consumeWindowInsets(innerPadding), contentPadding = innerPadding ) { items(count = 100) { Box( Modifier .fillMaxWidth() .height(50.dp) .background(colors[it % colors.size]) ) } } }
기본 인셋 재정의
컴포저블에 전달된 windowInsets
매개변수를 변경하여 컴포저블의 동작을 구성할 수 있습니다. 이 매개변수는 대신 적용할 다른 유형의 창 인셋이거나 빈 인스턴스(WindowInsets(0, 0, 0, 0)
)를 전달하여 사용 중지할 수 있습니다.
예를 들어 LargeTopAppBar
에서 인셋 처리를 사용 중지하려면 windowInsets
매개변수를 빈 인스턴스로 설정합니다.
LargeTopAppBar( windowInsets = WindowInsets(0, 0, 0, 0), title = { Text("Hi") } )
뷰 시스템 인셋과의 상호 운용성
화면에 동일한 계층 구조에 뷰와 Compose 코드가 모두 있는 경우 기본 인셋을 재정의해야 할 수 있습니다. 이 경우 어떤 뷰가 인셋을 사용해야 하고 어떤 뷰가 인셋을 무시해야 하는지 명시적으로 지정해야 합니다.
예를 들어 가장 바깥쪽 레이아웃이 Android 뷰 레이아웃인 경우 뷰 시스템에서 인셋을 사용하고 Compose에서는 무시해야 합니다.
또는 최외부 레이아웃이 컴포저블인 경우 Compose에서 인셋을 사용하고 AndroidView
컴포저블을 적절하게 패딩해야 합니다.
기본적으로 각 ComposeView
는 WindowInsetsCompat
소비 수준에서 모든 인셋을 사용합니다. 이 기본 동작을 변경하려면 ComposeView.consumeWindowInsets
를 false
로 설정합니다.
시스템 표시줄 보호
앱이 SDK 35 이상을 타겟팅하면 가득 채우기가 적용됩니다. 시스템 상태 표시줄과 동작 탐색 메뉴는 투명하지만 3버튼 탐색 메뉴는 반투명합니다.
기본 반투명 3버튼 탐색 배경 보호를 삭제하려면 Window.setNavigationBarContrastEnforced
를 false
로 설정합니다.
리소스
- Android 시스템 표시줄, 시스템 표시줄 디자인 가이드
- Now in Android: Kotlin과 Jetpack Compose로 완전히 빌드된 모든 기능을 갖춘 Android 앱입니다.
- Android 15에서 더 넓은 화면 시행 처리: Android 15 더 넓은 화면 시행을 안내하는 Codelab
- Android 15의 더 넓은 화면 시행을 위한 인셋 처리 팁
- 앱의 더 넓은 화면 UI 미리보기 및 테스트
- Android 앱 환경을 개선하는 3가지 방법: 더 넓은 화면, 뒤로 탐색 예측, Glance: Android 15 더 넓은 화면 적용에 관한 YouTube 동영상
- 가득 차게 표시 및 인셋 | Compose 도움말: 인셋을 처리하여 가득 차게 표시하는 방법을 보여주는 YouTube 동영상
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- Material 구성요소 및 레이아웃
CoordinatorLayout
를 Compose로 이전- 기타 고려사항