Android 태블릿·폴더블 화면 대응 완벽 가이드 — Window Size Classes, 반응형 XML, 화면 회전과 상태 유지

Android 앱을 개발할 때 가장 먼저 고려하는 기기는 일반적인 스마트폰입니다.

대부분의 앱은 세로 화면을 기준으로 UI를 설계하고, Activity와 Fragment를 이용해 화면을 이동하는 구조로 개발합니다.

하지만 Android 앱을 실행하는 기기는 스마트폰만 있는 것이 아닙니다.

태블릿, 폴더블 스마트폰, ChromeOS 기기, 데스크톱 창 모드 등 다양한 화면 환경이 존재합니다.

특히 폴더블 기기는 접힌 상태와 펼친 상태의 화면 크기가 크게 달라질 수 있습니다.

기존 스마트폰 UI를 단순히 확대하면 화면의 여백이 지나치게 커지거나, 목록과 상세 화면을 계속 왕복해야 하는 문제가 발생할 수 있습니다.

이번 글에서는 Kotlin + XML + Fragment 기반 앱에서 반응형 UI를 구현하는 방법을 살펴보겠습니다.

주요 내용은 다음과 같습니다.

  • Android 대화면 대응의 기본 개념

  • Window Size Classes

  • 화면 크기와 화면 방향의 차이

  • XML 리소스 한정자를 이용한 레이아웃 분리

  • Fragment를 이용한 목록·상세 2단 화면

  • Navigation Component와 상태 관리

  • 폴더블 기기의 접힘 상태와 힌지

  • 화면 회전과 Activity 재생성

  • ViewModel과 SavedStateHandle

  • 멀티윈도우와 자유 크기 조절

  • RecyclerView, ViewPager2, BottomNavigationView 최적화

  • 에뮬레이터 및 실제 기기 테스트

작성 기준: 2026년 10월. Kotlin, XML View, AndroidX, Fragment, Navigation Component, minSdk 24 이상을 기준으로 설명합니다. AndroidX WindowManager 및 Adaptive 관련 라이브러리 API는 버전에 따라 달라질 수 있으므로 실제 프로젝트에 적용하기 전 공식 문서를 확인해야 합니다.


1. Android 대화면 대응이 필요한 이유

일반적인 스마트폰 앱은 다음과 같은 화면 구조를 많이 사용합니다.

┌──────────────────┐
│      검색        │
├──────────────────┤
│ 참새             │
│ 직박구리         │
│ 까치             │
│ 박새             │
│                  │
│                  │
├──────────────────┤
│ 홈  검색  설정   │
└──────────────────┘

사용자가 참새를 선택하면 목록 화면에서 상세 화면으로 이동합니다.

하지만 태블릿에서는 넓은 공간을 활용할 수 있습니다.

┌─────────────────────────────────────────┐
│                 조류 도감               │
├─────────────────┬───────────────────────┤
│ 검색            │ 참새                  │
│                 │                       │
│ 참새            │      새 사진          │
│ 직박구리        │                       │
│ 까치            │ 학명: Passer montanus │
│ 박새            │                       │
│                 │ 상세 설명             │
│                 │                       │
└─────────────────┴───────────────────────┘

왼쪽에는 목록을, 오른쪽에는 선택한 새의 상세 정보를 표시할 수 있습니다.

이처럼 대화면 대응의 핵심은 단순히 UI를 확대하는 것이 아니라 사용 가능한 화면 공간에 맞게 정보 구조를 조정하는 것입니다.


화면 크기 이해하기

2. 화면 크기와 해상도는 다르다

Android 기기의 화면을 비교할 때 해상도만 확인하면 안 됩니다.

예를 들어 두 기기의 해상도가 비슷하더라도 화면 밀도에 따라 실제 UI에 사용할 수 있는 공간은 달라집니다.

Android 레이아웃에서는 일반적으로 dp 단위를 사용합니다.

android:layout_width="320dp"
android:layout_height="wrap_content"

dp는 화면 밀도를 고려한 논리적 크기 단위입니다.

반면 이미지 픽셀이나 실제 디스플레이 해상도는 다른 개념입니다.

반응형 UI를 구현할 때는 물리적 픽셀 수보다 현재 앱 창에서 사용할 수 있는 dp 단위의 공간을 중심으로 판단하는 것이 좋습니다.

3. 화면 방향만으로 레이아웃을 결정하면 안 되는 이유

기존 Android 앱에서는 다음과 같은 방식으로 화면 방향을 확인하는 경우가 많습니다.

val orientation =
    resources.configuration.orientation

if (orientation ==
    Configuration.ORIENTATION_LANDSCAPE
) {
    // 가로 화면
}

하지만 가로 화면이라고 해서 반드시 넓은 화면인 것은 아닙니다.

예를 들어 일반 스마트폰을 가로로 회전하면 높이가 매우 좁아질 수 있습니다.

반대로 태블릿은 세로 방향이어도 목록과 상세 화면을 함께 표시할 수 있을 만큼 넓을 수 있습니다.

따라서 화면 방향만으로 1단·2단 레이아웃을 결정하는 것은 적절하지 않습니다.


Window Size Classes

4. Window Size Classes란?

Window Size Classes는 앱이 사용할 수 있는 창의 크기를 기준으로 UI 구성을 판단하는 방법입니다.

일반적으로 다음과 같은 크기 범주를 활용합니다.

너비 범주대표적인 기준UI 예시
Compact600dp 미만단일 화면
Medium600dp 이상, 840dp 미만넓어진 단일 화면 또는 2단
Expanded840dp 이상목록·상세 2단
Large / Extra-large더 넓은 창다중 패널 및 여유 있는 레이아웃

Compact, Medium, Expanded는 오랫동안 사용된 대표적인 구분입니다.

최신 적응형 UI 지침에서는 Large 및 Extra-large 같은 추가 범주도 활용합니다.

다만 실제 레이아웃을 나누는 기준은 앱의 콘텐츠와 사용성에 따라 조정해야 합니다.

예를 들어 조류 도감 앱에서는 840dp 이상일 때 목록·상세 화면을 나누는 방식이 적절할 수 있습니다.

5. 중요한 것은 기기 크기가 아니라 창 크기

폴더블 기기를 펼쳤더라도 앱이 분할 화면으로 실행 중이라면 실제 앱 창은 좁을 수 있습니다.

반대로 외부 디스플레이에 연결된 스마트폰 앱은 넓은 창에서 실행될 수도 있습니다.

기기 화면 크기
      ≠
현재 앱 창 크기

따라서 다음과 같은 방식은 피하는 것이 좋습니다.

if (isTabletDevice) {
    showTwoPaneLayout()
}

기기가 태블릿인지 판별하기보다 현재 앱 창이 2단 레이아웃을 표시할 수 있을 만큼 넓은지 확인하는 것이 좋습니다.


AndroidX WindowManager 활용

6. WindowManager 라이브러리 추가

AndroidX WindowManager는 앱 창과 폴더블 기기의 레이아웃 특성을 확인할 때 사용할 수 있습니다.

build.gradle.kts에 다음 의존성을 추가할 수 있습니다.

dependencies {
    implementation(
        "androidx.window:window:<version>"
    )
}

<version>에는 AndroidX 공식 릴리스에서 확인한 안정 버전을 입력합니다.

WindowManager는 폴더블 기기의 접힘 상태와 디스플레이 특징을 확인할 때 특히 유용합니다.

일반적인 창 너비 분류에는 Window Size Class API 또는 실제 창 크기 측정 방법을 사용할 수 있습니다.

7. 현재 창 크기 측정하기

Activity의 현재 창 크기를 확인할 수 있습니다.

val windowMetrics =
    WindowMetricsCalculator
        .getOrCreate()
        .computeCurrentWindowMetrics(this)

val bounds = windowMetrics.bounds

다만 bounds.width()는 픽셀 단위입니다.

dp 단위로 변환하려면 화면 밀도를 고려해야 합니다.

val density =
    resources.displayMetrics.density

val widthDp =
    bounds.width() / density

이 코드는 기본 개념을 설명하는 예제입니다.

실제 프로젝트에서는 Window Size Class API를 사용해 창 크기를 분류하는 것이 더 편리할 수 있습니다.

또한 창 크기를 측정할 때 시스템 바와 인셋이 실제 콘텐츠 영역에 어떤 영향을 주는지도 고려해야 합니다.


XML 리소스로 반응형 UI 만들기

8. 가장 간단한 방법: layout-sw600dp

기존 XML 기반 Android 앱이라면 리소스 한정자를 활용할 수 있습니다.

예를 들어 다음과 같이 구성합니다.

res/
 ├── layout/
 │    └── activity_main.xml
 │
 └── layout-sw600dp/
      └── activity_main.xml

layout-sw600dp는 기기의 사용 가능한 최소 너비 특성에 따라 리소스를 선택하는 방식입니다.

태블릿 등에서 자주 활용할 수 있습니다.

하지만 현재 앱 창 크기에 따라 즉시 레이아웃을 변경해야 하는 멀티윈도우 대응에서는 sw 한정자만으로 충분하지 않을 수 있습니다.

smallestWidth는 현재 창 너비와 동일한 개념이 아니기 때문입니다.

9. 현재 너비 기준의 layout-w600dp

현재 사용할 수 있는 너비에 더 직접적으로 대응하려면 w 한정자를 고려할 수 있습니다.

res/
 ├── layout/
 │    └── activity_main.xml
 │
 └── layout-w600dp/
      └── activity_main.xml

이렇게 하면 현재 리소스 구성에서 사용 가능한 너비가 기준을 충족할 때 다른 레이아웃을 선택할 수 있습니다.

다만 실제 창 크기 변경 시 Activity가 재생성되는지, 구성 변경을 직접 처리하는지에 따라 적용 방식이 달라질 수 있습니다.

따라서 폴더블 및 멀티윈도우 환경에서는 리소스 한정자와 런타임 창 크기 대응을 함께 고려하는 것이 좋습니다.


목록·상세 2단 레이아웃 구현

10. 조류 도감 앱을 예제로 살펴보기

스마트폰에서는 다음과 같은 화면 이동을 사용한다고 가정하겠습니다.

BirdListFragment
        │
        │ 항목 선택
        ▼
BirdDetailFragment

태블릿에서는 다음 구조가 적합할 수 있습니다.

MainActivity
      │
      ├── BirdListFragment
      │
      └── BirdDetailFragment

이처럼 같은 Fragment를 재사용하면서 배치 방식만 변경하면 기존 앱을 완전히 새로 개발할 필요가 없습니다.

11. 스마트폰용 XML

res/layout/activity_main.xml 예제입니다.

<?xml version="1.0" encoding="utf-8"?>

<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

스마트폰에서는 하나의 Fragment 영역만 사용합니다.

목록을 선택하면 상세 Fragment로 전환합니다.

12. 대화면용 XML

res/layout-w840dp/activity_main.xml을 생성할 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/listContainer"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:background="#DDDDDD" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/detailContainer"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2" />

</LinearLayout>

왼쪽 목록과 오른쪽 상세 화면의 비율을 1:2로 구성한 예제입니다.

실제 앱에서는 목록의 최소 너비, 화면 여백, 최대 콘텐츠 너비 등을 고려하는 것이 좋습니다.


13. Activity에서 레이아웃 구분하기

레이아웃에 detailContainer가 존재하는지 확인해 2단 모드를 판단할 수 있습니다.

val isTwoPane =
    findViewById<View?>(
        R.id.detailContainer
    ) != null

이 방식은 XML 리소스에 따라 레이아웃이 분리된 경우에 사용할 수 있습니다.

다만 R.id.detailContainer는 모든 레이아웃에서 참조 가능하도록 리소스 ID가 정의되어 있어야 합니다.

14. Fragment 배치하기

다음은 개념적인 구현 예제입니다.

if (savedInstanceState == null) {

    if (isTwoPane) {

        supportFragmentManager
            .beginTransaction()
            .replace(
                R.id.listContainer,
                BirdListFragment()
            )
            .replace(
                R.id.detailContainer,
                BirdDetailFragment()
            )
            .commit()

    } else {

        supportFragmentManager
            .beginTransaction()
            .replace(
                R.id.mainContainer,
                BirdListFragment()
            )
            .commit()
    }
}

BirdDetailFragment는 선택된 항목이 없을 때 빈 상태 화면을 표시할 수 있도록 구현해야 합니다.

이 코드는 기본적인 Fragment 배치 예제입니다.

실제 앱에서는 화면 크기 변경에 따른 Fragment 복원, 컨테이너 변경, 백스택 처리를 함께 설계해야 합니다.


목록과 상세 화면의 데이터 공유

15. Activity 범위 ViewModel 사용하기

2단 레이아웃에서는 목록에서 선택한 새 정보를 상세 화면에 전달해야 합니다.

이때 Activity 범위의 공유 ViewModel을 사용할 수 있습니다.

class BirdSelectionViewModel : ViewModel() {

    private val _selectedBirdId =
        MutableStateFlow<String?>(null)

    val selectedBirdId =
        _selectedBirdId.asStateFlow()

    fun selectBird(id: String) {
        _selectedBirdId.value = id
    }
}

목록 Fragment에서는 선택한 새의 ID를 전달합니다.

private val selectionViewModel:
    BirdSelectionViewModel by activityViewModels()

private fun onBirdClicked(bird: BirdItem) {
    selectionViewModel.selectBird(bird.ktsn)
}

상세 Fragment에서는 선택된 ID를 관찰합니다.

viewLifecycleOwner.lifecycleScope.launch {

    viewLifecycleOwner.repeatOnLifecycle(
        Lifecycle.State.STARTED
    ) {

        selectionViewModel.selectedBirdId
            .collectLatest { birdId ->

                if (birdId != null) {
                    loadBirdDetail(birdId)
                }
            }
    }
}

이렇게 하면 목록과 상세 화면이 직접 서로를 참조하지 않아도 됩니다.

다만 이 예제의 ViewModel은 메모리에만 선택 상태를 유지합니다.

프로세스가 종료된 이후에도 복원하려면 SavedStateHandle 등을 함께 사용해야 합니다.


Navigation Component 연동

16. 단일 화면과 2단 화면의 내비게이션

스마트폰에서는 일반적으로 목록에서 상세 화면으로 이동합니다.

findNavController().navigate(
    R.id.action_list_to_detail,
    bundleOf(
        "birdId" to bird.ktsn
    )
)

하지만 태블릿에서는 상세 Fragment를 교체하지 않고 선택된 항목만 변경하는 것이 자연스러울 수 있습니다.

스마트폰:
목록 → 상세 화면 이동

태블릿:
목록 선택 → 오른쪽 상세 내용 변경

따라서 항목 선택 이벤트와 실제 화면 이동 로직을 분리하는 것이 좋습니다.

17. 화면 크기에 따라 동작 분리하기

예를 들어 다음과 같은 인터페이스를 정의할 수 있습니다.

interface BirdSelectionHandler {
    fun onBirdSelected(birdId: String)
}

Activity에서는 현재 레이아웃에 맞게 처리합니다.

override fun onBirdSelected(
    birdId: String
) {

    if (isTwoPane) {

        selectionViewModel.selectBird(birdId)

    } else {

        // Navigation Component로
        // 상세 화면 이동
    }
}

이렇게 하면 RecyclerView Adapter나 목록 Fragment가 화면 크기별 내비게이션 정책을 직접 알 필요가 없습니다.


폴더블 기기 대응

18. 폴더블 기기는 일반 태블릿과 다르다

폴더블 기기는 사용 중 화면 상태가 바뀔 수 있습니다.

예를 들어 다음과 같습니다.

접힌 상태
┌─────────────┐
│ 목록        │
│             │
│ 참새        │
│ 직박구리    │
│ 까치        │
└─────────────┘
펼친 상태
┌──────────────────────────┐
│ 목록      │ 상세 정보    │
│           │              │
│ 참새      │ 참새 사진    │
│ 직박구리  │ 학명         │
│ 까치      │ 설명         │
└──────────────────────────┘

사용자가 앱을 실행한 상태에서 기기를 펼칠 수 있기 때문에 화면 전환과 상태 복원을 고려해야 합니다.

19. FoldingFeature란?

AndroidX WindowManager에서는 폴더블 기기의 접힘 관련 정보를 확인할 수 있습니다.

대표적인 API가 FoldingFeature입니다.

이 정보를 이용하면 다음 사항을 파악할 수 있습니다.

  • 접힘 영역의 위치

  • 접힘 상태

  • 화면을 분리하는 힌지 여부

  • 접힘 방향

다만 모든 폴더블 기기에서 동일한 형태의 힌지가 존재하는 것은 아닙니다.

또한 화면이 넓다고 해서 반드시 접힘 영역이 있는 것은 아닙니다.

20. 접힘 정보 관찰하기

Activity에서 WindowInfoTracker를 사용할 수 있습니다.

lifecycleScope.launch {

    repeatOnLifecycle(
        Lifecycle.State.STARTED
    ) {

        WindowInfoTracker
            .getOrCreate(this@MainActivity)
            .windowLayoutInfo(this@MainActivity)
            .collect { layoutInfo ->

                val foldingFeature =
                    layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()

                handleFoldingFeature(
                    foldingFeature
                )
            }
    }
}

위 코드는 WindowManager의 Flow API를 활용한 예제입니다.

handleFoldingFeature()는 앱에서 별도로 구현해야 합니다.

접힘 정보가 없는 기기에서는 foldingFeature가 null일 수 있으므로 반드시 처리해야 합니다.


21. 힌지를 가로지르는 UI 피하기

일부 폴더블 기기에는 화면 중앙에 물리적인 힌지나 접힘 영역이 존재합니다.

이 영역을 가로질러 중요한 버튼이나 텍스트를 배치하면 사용성이 떨어질 수 있습니다.

예를 들어 다음과 같은 레이아웃은 문제가 될 수 있습니다.

┌──────────────┬──────────────┐
│              │              │
│       중요한 버튼           │
│              │              │
└──────────────┴──────────────┘
               ↑
             힌지

가능하면 힌지를 기준으로 콘텐츠를 분리하거나 안전한 영역에 배치하는 것이 좋습니다.

다만 모든 접힘 영역이 콘텐츠를 완전히 가리는 것은 아니므로 FoldingFeature의 속성을 확인해야 합니다.


화면 회전과 상태 유지

22. Activity는 화면 회전 시 재생성될 수 있다

Android에서는 화면 방향이나 구성 변경이 발생하면 Activity가 재생성될 수 있습니다.

Activity 실행
      ↓
화면 회전
      ↓
기존 Activity 종료
      ↓
새 Activity 생성

이 과정에서 UI에만 저장한 상태는 사라질 수 있습니다.

예를 들어 사용자가 검색어를 입력하고 특정 새를 선택한 상태라면 회전 이후에도 그 상태를 유지하는 것이 좋습니다.

23. ViewModel을 이용한 상태 유지

ViewModel은 일반적인 구성 변경으로 Activity가 재생성될 때 UI 상태를 유지하는 데 유용합니다.

class BirdListViewModel : ViewModel() {

    private val _query =
        MutableStateFlow("")

    val query = _query.asStateFlow()

    fun updateQuery(value: String) {
        _query.value = value
    }
}

하지만 ViewModel만으로 모든 상황을 해결할 수는 없습니다.

앱 프로세스가 시스템에 의해 종료되면 ViewModel도 사라집니다.

따라서 중요한 상태는 SavedStateHandle이나 영구 저장소에 보관해야 합니다.


24. SavedStateHandle 활용하기

선택된 새 ID를 복원하려면 다음과 같이 구현할 수 있습니다.

class BirdSelectionViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val selectedBirdId =
        savedStateHandle.getStateFlow<String?>(
            "selectedBirdId",
            null
        )

    fun selectBird(id: String) {

        savedStateHandle[
            "selectedBirdId"
        ] = id
    }
}

이렇게 하면 Android가 저장 상태를 복원할 수 있는 상황에서 선택 정보를 복원할 수 있습니다.

다만 SavedStateHandle은 영구 데이터베이스가 아닙니다.

사용자가 반드시 보존해야 하는 메모, 좋아요, 관찰 기록 등은 SQLite와 같은 영구 저장소에 저장해야 합니다.


25. 화면 크기 변경 시 Fragment 복원 문제

스마트폰에서는 하나의 Fragment 컨테이너를 사용하고, 태블릿에서는 두 개의 컨테이너를 사용할 수 있습니다.

이때 화면 크기가 변경되면서 Activity가 재생성되면 FragmentManager가 이전 Fragment 상태를 복원하려고 할 수 있습니다.

그런데 이전 컨테이너 ID가 새 레이아웃에 존재하지 않으면 문제가 발생할 수 있습니다.

따라서 단순히 savedInstanceState == null 조건만으로 Fragment를 추가하는 구조는 실제 폴더블 대응에서 충분하지 않을 수 있습니다.

다음과 같은 방법을 고려할 수 있습니다.

  • 공통 Fragment 컨테이너 ID 유지

  • 화면 크기 변경에 맞는 Fragment 배치 정책 설계

  • Navigation Component의 백스택과 복원 동작 검증

  • 선택된 항목 ID를 ViewModel과 SavedStateHandle로 관리

  • 창 크기 변경 시 Fragment를 안전하게 재배치

특히 1단과 2단 레이아웃 전환은 단순한 XML 교체 문제가 아니라 내비게이션 상태 관리 문제이기도 합니다.


RecyclerView 대화면 최적화

26. 목록 폭이 지나치게 넓어지는 문제

태블릿에서 RecyclerView를 match_parent로 표시하면 카드가 지나치게 넓어질 수 있습니다.

예를 들어 새 사진과 이름만 있는 목록이 화면 전체 너비를 차지한다면 정보 밀도가 낮아집니다.

이 경우 다음 방법을 고려할 수 있습니다.

  • 목록의 최대 너비 제한

  • GridLayoutManager 사용

  • 목록·상세 2단 구성

  • 카드 내부 레이아웃 변경

27. GridLayoutManager 활용

화면 크기에 따라 열 수를 변경할 수 있습니다.

val spanCount =
    if (isExpandedWindow) 3 else 1

binding.recyclerView.layoutManager =
    GridLayoutManager(
        requireContext(),
        spanCount
    )

여기서 isExpandedWindow는 현재 창 크기를 기준으로 계산한 값이라고 가정합니다.

실제 앱에서는 카드의 최소 너비를 기준으로 열 수를 결정하는 것이 더 자연스러울 수 있습니다.

예를 들어 화면 너비가 넓어질수록 2열, 3열, 4열로 확장할 수 있습니다.


ViewPager2 대화면 대응

28. 이미지 갤러리의 최대 크기 제한

조류 도감 상세 화면에서 ViewPager2를 이용해 사진을 표시한다고 가정하겠습니다.

스마트폰에서는 화면 너비를 가득 채우는 이미지가 자연스럽습니다.

하지만 태블릿에서는 이미지가 지나치게 확대될 수 있습니다.

이 경우 이미지 영역에 최대 너비를 설정하거나 상세 화면의 콘텐츠 영역을 제한하는 것이 좋습니다.

또한 Glide를 사용한다면 실제 표시 크기에 맞는 이미지 로딩 정책을 적용해야 합니다.

원본 이미지를 무조건 최대 크기로 디코딩하면 메모리 사용량이 증가할 수 있습니다.


내비게이션 UI 변경

29. BottomNavigationView와 NavigationRailView

스마트폰에서는 BottomNavigationView가 일반적입니다.

┌──────────────────┐
│                  │
│      콘텐츠      │
│                  │
├──────────────────┤
│ 홈  검색  설정   │
└──────────────────┘

반면 넓은 화면에서는 NavigationRailView를 사용할 수 있습니다.

┌──────┬─────────────────────┐
│ 홈   │                     │
│      │                     │
│ 검색 │       콘텐츠        │
│      │                     │
│ 설정 │                     │
└──────┴─────────────────────┘

NavigationRailView는 Material Components에서 제공하는 내비게이션 UI입니다.

화면 크기에 따라 BottomNavigationView와 NavigationRailView를 전환하는 구조를 만들 수 있습니다.

30. XML에서 내비게이션 분리하기

스마트폰용 레이아웃에서는 다음과 같이 사용할 수 있습니다.

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottomNavigation"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:menu="@menu/main_navigation" />

대화면용 레이아웃에서는 다음과 같이 구성할 수 있습니다.

<com.google.android.material.navigationrail.NavigationRailView
    android:id="@+id/navigationRail"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    app:menu="@menu/main_navigation" />

두 UI가 같은 메뉴 리소스를 사용하도록 구성하면 관리하기 쉽습니다.

다만 Navigation Component와 연결하는 방식, 선택 상태 유지, 백스택 정책은 함께 설계해야 합니다.


멀티윈도우 대응

31. 멀티윈도우에서는 창 크기가 계속 달라질 수 있다

Android에서는 앱을 분할 화면이나 자유 형식 창으로 실행할 수 있습니다.

예를 들어 태블릿에서 두 앱을 나란히 실행하면 하나의 앱이 사용할 수 있는 너비가 줄어듭니다.

┌────────────────┬────────────────┐
│ 조류 도감 앱   │ 브라우저       │
│                │                │
│                │                │
└────────────────┴────────────────┘

따라서 태블릿이라고 해서 항상 2단 레이아웃을 유지해서는 안 됩니다.

현재 앱 창이 좁아졌다면 다시 단일 화면으로 전환할 수 있어야 합니다.

32. Activity 재생성에 의존할 것인가?

창 크기가 변경되면 시스템 구성 변경이 발생할 수 있습니다.

기본적으로 Android가 Activity를 재생성하도록 두고 리소스에 맞는 UI를 다시 구성하는 방식이 비교적 단순합니다.

반대로 android:configChanges를 이용해 구성 변경을 직접 처리할 수도 있습니다.

하지만 직접 처리하면 개발자가 레이아웃, 리소스, 상태 변경을 더 많이 관리해야 합니다.

따라서 특별한 이유가 없다면 구성 변경을 무조건 막는 방식은 피하는 것이 좋습니다.


Edge-to-edge와 인셋

33. 대화면에서도 WindowInsets가 중요하다

Android 15 이후의 Edge-to-edge 대응은 스마트폰뿐 아니라 태블릿과 폴더블에서도 중요합니다.

특히 다음 영역을 고려해야 합니다.

  • 상태 표시줄

  • 내비게이션 바

  • 디스플레이 컷아웃

  • 키보드 영역

  • 시스템 UI와 겹치는 콘텐츠

예를 들어 다음과 같이 인셋을 적용할 수 있습니다.

ViewCompat.setOnApplyWindowInsetsListener(
    binding.root
) { view, insets ->

    val systemBars =
        insets.getInsets(
            WindowInsetsCompat.Type.systemBars()
        )

    view.updatePadding(
        left = systemBars.left,
        top = systemBars.top,
        right = systemBars.right,
        bottom = systemBars.bottom
    )

    insets
}

다만 기존 XML 패딩이 있다면 이를 보존해야 합니다.

위 코드는 인셋 값으로 패딩을 직접 설정하므로 기존 패딩을 덮어쓸 수 있습니다.

실제 프로젝트에서는 초기 패딩을 저장한 뒤 인셋을 더하는 방식이 안전합니다.


테스트 환경 구성

34. Android Studio 에뮬레이터 활용

Android Studio에서는 다양한 화면 크기의 가상 기기를 생성할 수 있습니다.

대표적으로 다음 환경을 테스트할 수 있습니다.

  • 일반 스마트폰

  • 대형 스마트폰

  • 폴더블 기기

  • 7~8인치 태블릿

  • 10인치 이상 태블릿

  • 가로 화면

  • 분할 화면

폴더블 에뮬레이터에서는 접힘 상태를 변경하며 화면 전환을 테스트할 수 있습니다.

다만 실제 기기의 힌지 구조, 제조사 런처, 화면 비율 등은 에뮬레이터와 차이가 있을 수 있습니다.


35. 테스트해야 할 화면 상태

테스트 환경확인 항목
일반 스마트폰 세로단일 화면
일반 스마트폰 가로텍스트 잘림 및 스크롤
폴더블 접힘목록 탐색
폴더블 펼침2단 레이아웃
태블릿 세로적절한 열 수
태블릿 가로콘텐츠 폭과 여백
분할 화면창 너비 변경 대응
자유 크기 창동적 레이아웃 변경
화면 회전선택 상태 유지
프로세스 재생성검색어 및 선택 항목 복원

특히 폴더블에서는 접힘과 펼침을 반복해도 선택된 데이터와 스크롤 위치가 유지되는지 확인하는 것이 중요합니다.


실무 적용 전략

36. 기존 앱을 한 번에 수정해야 할까?

이미 출시한 XML 기반 앱이라면 모든 화면을 한 번에 변경할 필요는 없습니다.

다음과 같은 순서로 개선하는 것이 좋습니다.

1단계: 화면 크기 문제 확인

태블릿과 폴더블 에뮬레이터에서 기존 앱을 실행합니다.

화면이 지나치게 늘어나거나 글자가 잘리는 영역을 확인합니다.

2단계: 콘텐츠 최대 너비 조정

설정 화면, 입력 화면, 상세 설명 등은 적절한 최대 너비를 적용합니다.

3단계: 목록 화면 최적화

RecyclerView를 GridLayoutManager로 변경하거나 카드 너비를 조정합니다.

4단계: 목록·상세 2단 구성

검색 결과와 상세 정보를 동시에 표시할 수 있도록 개선합니다.

5단계: 내비게이션 변경

넓은 화면에서 NavigationRailView 사용을 검토합니다.

6단계: 상태 유지 테스트

화면 회전, 폴더블 접힘·펼침, 멀티윈도우 전환을 테스트합니다.


37. 조류 도감 앱 추천 구조

조류 도감 앱에서는 다음과 같은 구조를 추천합니다.

MainActivity
      │
      ├── WindowSizePolicy
      │
      ├── BirdSelectionViewModel
      │
      ├── BirdListFragment
      │      │
      │      └── RecyclerView
      │
      └── BirdDetailFragment
             │
             ├── ViewPager2
             ├── 상세 정보
             └── 좋아요·메모

WindowSizePolicy는 현재 창 크기에 따라 레이아웃 정책을 결정하는 역할을 담당합니다.

예를 들어 다음과 같은 값을 사용할 수 있습니다.

enum class WindowLayoutMode {
    SINGLE_PANE,
    TWO_PANE
}

이렇게 하면 UI 구성 정책을 여러 Fragment에 분산시키지 않고 한곳에서 관리할 수 있습니다.


38. Android 대화면 대응 체크리스트

  • 화면 방향만으로 레이아웃을 결정하지 않음

  • 현재 앱 창 크기를 기준으로 UI 구성

  • Window Size Classes 이해

  • Compact, Medium, Expanded 대응

  • XML 리소스 한정자 활용

  • sw와 w 한정자의 차이 이해

  • 목록·상세 2단 레이아웃 검토

  • Fragment 재사용 구조 설계

  • Navigation Component 백스택 확인

  • Activity 범위 ViewModel 활용

  • SavedStateHandle로 선택 상태 복원

  • 폴더블 접힘 상태 확인

  • 힌지 영역과 콘텐츠 겹침 방지

  • RecyclerView 열 수 조정

  • ViewPager2 이미지 크기 최적화

  • NavigationRailView 적용 검토

  • 멀티윈도우 크기 변경 대응

  • 화면 회전 후 상태 유지

  • Edge-to-edge 및 WindowInsets 처리

  • 태블릿 에뮬레이터 테스트

  • 폴더블 에뮬레이터 테스트

  • 실제 기기에서 최종 검증


39. 마무리

Android 앱의 대화면 대응은 단순히 태블릿용 XML을 하나 더 만드는 작업이 아닙니다.

사용자가 어떤 크기의 창에서 앱을 실행하더라도 콘텐츠를 편리하게 탐색할 수 있도록 UI 구조를 설계하는 과정입니다.

특히 폴더블 기기에서는 앱 실행 중 화면 크기가 변경될 수 있으므로 상태 관리와 내비게이션 구조가 중요합니다.

이번 글의 핵심을 정리하면 다음과 같습니다.

  1. 기기 종류보다 현재 앱 창 크기를 기준으로 판단합니다.

  2. Window Size Classes를 활용해 레이아웃 정책을 결정합니다.

  3. 기존 XML 앱도 리소스 한정자를 활용해 대화면에 대응할 수 있습니다.

  4. 목록·상세 화면을 2단으로 구성하면 넓은 공간을 효율적으로 사용할 수 있습니다.

  5. Fragment와 ViewModel을 재사용하면 기존 앱을 전면 재개발할 필요가 없습니다.

  6. 폴더블 기기에서는 힌지와 접힘 상태를 고려해야 합니다.

  7. 화면 회전과 창 크기 변경 시 UI 상태를 유지해야 합니다.

  8. RecyclerView, ViewPager2, 내비게이션 UI도 화면 크기에 맞게 조정합니다.

  9. 멀티윈도우에서는 태블릿도 좁은 창으로 실행될 수 있습니다.

  10. 에뮬레이터와 실제 기기에서 다양한 창 크기를 테스트해야 합니다.

기존 스마트폰 앱을 운영하고 있다면 우선 태블릿 에뮬레이터에서 실행해 보는 것부터 시작해도 좋습니다.

화면이 넓어졌을 때 어떤 정보가 함께 표시되면 편리할지 고민하는 것만으로도 개선 방향을 찾을 수 있습니다.

Android 대화면 대응의 핵심은 큰 화면을 채우는 것이 아니라, 화면 크기에 맞는 더 나은 사용 경험을 제공하는 것입니다.


참고 자료

태그: Android, Kotlin, 태블릿, 폴더블, Foldable, Window Size Classes, WindowManager, 반응형 XML, Fragment, ViewModel, SavedStateHandle, Navigation Component, RecyclerView, ViewPager2, NavigationRailView, 멀티윈도우, Android 대화면 대응