본문 바로가기
delphi

Jetpack Compose HorizontalPager에서 NavController로 화면 이동하는 방법

by 천지조율 2025. 6. 20.

Jetpack Compose HorizontalPager와 NavController의 이해

Jetpack Compose는 안드로이드의 최신 UI 툴킷으로, 선언형 UI 방식을 통해 빠르고 효율적인 앱 개발을 가능하게 합니다. 특히 HorizontalPager는 화면을 좌우로 스와이프할 수 있는 페이지 뷰를 구성할 때 유용합니다. 하지만 문제는 NavController와의 연계입니다. Compose의 NavController는 화면 간 네비게이션을 관리하는 핵심 컴포넌트인데, 이 두 가지를 자연스럽게 연결하려면 명확한 이해가 필요합니다.

HorizontalPager에서 NavController로 이동하려는 이유

대부분의 개발자는 스와이프 동작버튼 클릭 또는 특정 이벤트 발생 시 다른 화면으로 이동하고 싶어합니다. 예를 들어, 사용자가 HorizontalPager에서 특정 페이지를 보고 있을 때, 그 페이지의 상세 화면으로 네비게이션하는 시나리오가 자주 발생합니다. 이때 단순히 pagerState.currentPage를 읽는 것만으로는 부족하며, NavController의 navigate() 함수와 정확히 연동해야 합니다.

HorizontalPager 핵심 구성 요소

PagerState의 역할

PagerState는 현재 페이지 번호, 애니메이션 상태, 최대 페이지 수 등을 관리합니다. rememberPagerState()로 초기화하며, 다음과 같은 속성을 제공합니다:

  • currentPage: 현재 보이는 페이지 인덱스
  • currentPageOffsetFraction: 현재 페이지의 오프셋 (애니메이션 시 유용)
  • isScrollInProgress: 스크롤 중 여부

이를 통해 사용자가 어느 페이지에 있는지 실시간으로 감지할 수 있습니다.

HorizontalPager 기본 코드 예시

val pagerState = rememberPagerState()
HorizontalPager(count = 3, state = pagerState) { page ->
    Text("Page: $page")
}

하지만 여기까지만으로는 NavController로의 이동이 구현되지 않습니다.

반응형

NavController와 HorizontalPager 연결하기

1. NavController 초기화

NavController는 rememberNavController()로 생성합니다.

val navController = rememberNavController()

이제 NavHost를 통해 각 화면의 목적지(route)를 정의해야 합니다.

 

2. NavHost 설정

NavHost는 Compose에서 네비게이션의 출발점입니다.

NavHost(navController, startDestination = "pager") {
    composable("pager") { PagerScreen(navController) }
    composable("details/{page}") { backStackEntry ->
        val page = backStackEntry.arguments?.getString("page")
        DetailsScreen(page)
    }
}

여기서 details/{page}는 페이지별 상세화면을 위해 동적 라우트를 사용합니다.

3. PagerScreen에서 NavController 사용

이제 HorizontalPager 내에서 NavController를 호출할 수 있습니다.

@Composable
fun PagerScreen(navController: NavController) {
    val pagerState = rememberPagerState()

    HorizontalPager(count = 3, state = pagerState) { page ->
        Box(modifier = Modifier
            .fillMaxSize()
            .clickable {
                navController.navigate("details/$page")
            }) {
            Text("Page: $page")
        }
    }
}

위 코드에서 각 페이지를 클릭하면 해당 페이지 번호를 포함한 상세화면으로 이동합니다.

반응형

실제 구현에서 주의할 점

동적 인자 전달

details/{page}와 같은 동적 라우트는 인자 전달 시 타입 변환에 유의해야 합니다. 예를 들어 Int 타입을 문자열로 변환해 주어야 하며, 필요하다면 NavType을 명시적으로 지정할 수도 있습니다.

애니메이션 중 중복 이벤트 차단

애니메이션이나 스와이프 중에 중복으로 navigate를 호출하면 앱이 충돌하거나 중복 화면이 쌓일 수 있습니다. 이를 방지하려면 isScrollInProgress를 체크하거나 LaunchedEffect로 debounce 처리를 추가하는 것이 좋습니다.

if (!pagerState.isScrollInProgress) {
    navController.navigate("details/$page")
}

BackStack 관리

페이지 이동 후 뒤로 가기(back) 동작이 기대한 대로 작동하는지 테스트해야 합니다. 필요하다면 popBackStack() 또는 popUpTo()를 사용해 스택 정리를 명확히 해야 합니다.

navController.navigate("details/$page") {
    popUpTo("pager") { inclusive = false }
}
반응형

실전 팁: 고급 패턴 적용

딥링크와 연계

NavController는 외부 앱에서의 딥링크 호출도 처리할 수 있습니다. 예를 들어, 특정 URL에서 바로 details/{page}로 진입할 수 있도록 설정하면 사용자 경험이 크게 향상됩니다.

Compose Navigation 애니메이션

Accompanist 라이브러리의 AnimatedNavHost를 사용하면 네비게이션 간 전환 애니메이션을 추가할 수 있습니다.

AnimatedNavHost(navController, startDestination = "pager") {
    // composable 정의
}

이렇게 하면 화면 이동이 더 자연스러워집니다.

코드 최적화 및 테스트 전략

상태 관리 최적화

remember로 관리하는 상태들은 반드시 불필요한 recomposition을 막도록 주의해야 합니다. 또한, rememberSaveable을 사용하면 화면 회전 시에도 상태가 유지됩니다.

테스트 코드 작성

네비게이션 동작은 UI 테스트에서 특히 중요합니다. Compose Testing 라이브러리를 활용해 각 라우트 전환이 올바르게 작동하는지 테스트를 작성하는 것이 권장됩니다.

composeTestRule.onNodeWithText("Page: 1").performClick()
composeTestRule.onNodeWithText("Details for page 1").assertIsDisplayed()

접근성 고려

contentDescription을 추가해 화면 리더에서의 접근성을 보장하는 것도 중요합니다.

Box(modifier = Modifier
    .fillMaxSize()
    .clickable {
        navController.navigate("details/$page")
    }
    .semantics { contentDescription = "Page $page" }) {
    Text("Page: $page")
}
반응형

결론

Jetpack Compose에서 HorizontalPager와 NavController를 자연스럽게 연동하는 것은 앱 내비게이션의 핵심입니다. 위에서 설명한 단계별 접근법을 따르면, 사용자가 스와이프나 클릭으로 상세 화면으로 매끄럽게 이동할 수 있으며, 더 나아가 딥링크, 애니메이션, 고급 상태 관리까지 확장할 수 있습니다.

이러한 고급 패턴을 프로젝트에 적용함으로써 앱의 완성도는 한층 높아지고, 사용자 경험 역시 크게 개선됩니다. 철저한 코드 최적화와 테스트로 안정성을 확보한다면, 경쟁 앱을 능가하는 훌륭한 Compose 앱을 만들 수 있을 것입니다.