지원 창 레이아웃 빌드

지원 창 레이아웃은 관련 지원 정보를 표시하면서 사용자의 관심을 앱의 기본 콘텐츠에 유지합니다. 예를 들어 기본 창에는 영화에 관한 세부정보가 표시되고 보조 창에는 유사한 영화, 동일한 감독의 영화 또는 동일한 배우가 출연한 작품이 표시될 수 있습니다.

자세한 내용은 Material 3 지원 창 가이드라인을 참고하세요.

NavigableSupportingPaneScaffold를 사용하여 지원 창 구현

NavigableSupportingPaneScaffold는 Jetpack Compose에서 지원 창 레이아웃을 간소화하는 컴포저블입니다. SupportingPaneScaffold를 래핑하고 내장 탐색 및 뒤로 탐색 예측 처리를 추가합니다.

지원 창 스케폴드는 최대 3개의 창을 지원합니다.

  • 기본 창: 기본 콘텐츠를 표시합니다.
  • 지원 창: 기본 창과 관련된 추가 컨텍스트 또는 도구를 제공합니다.
  • 추가 창 (선택사항): 필요한 경우 보충 콘텐츠에 사용됩니다.

스켈레톤은 창 크기에 따라 조정됩니다.

  • 대형 창에서는 기본 창과 지원 창이 나란히 표시됩니다.
  • 작은 창에서는 한 번에 하나의 창만 표시되며 사용자가 탐색할 때 전환됩니다.

    기본 콘텐츠가 디스플레이의 대부분을 차지하고 지원 콘텐츠가 함께 표시됩니다.
    그림 1. 지원 창 레이아웃

종속 항목 추가

NavigableSupportingPaneScaffoldMaterial 3 적응형 레이아웃 라이브러리의 일부입니다.

앱 또는 모듈의 build.gradle 파일에 다음과 같은 관련 종속 항목 세 개를 추가합니다.

Kotlin

implementation("androidx.compose.material3.adaptive:adaptive")
implementation("androidx.compose.material3.adaptive:adaptive-layout")
implementation("androidx.compose.material3.adaptive:adaptive-navigation")

Groovy

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • 적응형: HingeInfoPosture와 같은 하위 수준 빌딩 블록
  • adaptive-layout: ListDetailPaneScaffoldSupportingPaneScaffold와 같은 적응형 레이아웃
  • adaptive-navigation: 창 내부 및 간에 탐색하기 위한 컴포저블과 기본적으로 탐색을 지원하는 적응형 레이아웃(예: NavigableListDetailPaneScaffoldNavigableSupportingPaneScaffold)

프로젝트에 compose-material3-adaptive 버전 1.1.0-beta1 이상이 포함되어 있는지 확인합니다.

뒤로 탐색 예측 동작 선택

Android 15 이하에서 뒤로 탐색 예측 애니메이션을 사용 설정하려면 뒤로 탐색 예측 동작을 지원하도록 선택해야 합니다. 선택하려면 AndroidManifest.xml 파일의 <application> 태그에 android:enableOnBackInvokedCallback="true"를 추가하거나 <application> 태그 또는 개별 <activity> 태그에 android:enableOnBackInvokedCallback="true"를 추가합니다.

앱이 Android 16 (API 수준 36) 이상을 타겟팅하면 뒤로 탐색 예측이 기본적으로 사용 설정됩니다.

탐색기 만들기

작은 창에서는 한 번에 하나의 창만 표시되므로 ThreePaneScaffoldNavigator를 사용하여 창 간에 이동합니다. rememberSupportingPaneScaffoldNavigator를 사용하여 탐색기 인스턴스를 만듭니다.

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

탐색기를 스캐폴드에 전달

스캐폴드에는 스캐폴드의 상태를 나타내는 인터페이스인 ThreePaneScaffoldNavigator, ThreePaneScaffoldValue, PaneScaffoldDirective가 필요합니다.

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = { /*...*/ },
    supportingPane = { /*...*/ },
)

기본 창과 지원 창은 콘텐츠가 포함된 컴포저블입니다. AnimatedPane를 사용하여 탐색 중에 기본 창 애니메이션을 적용합니다. 스캐폴드 값을 사용하여 지원 창이 숨겨져 있는지 확인합니다. 숨겨져 있으면 navigateTo(SupportingPaneScaffoldRole.Supporting)를 호출하여 지원 창을 표시하는 버튼을 표시합니다.

다음은 스캐폴드의 전체 구현입니다.

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        AnimatedPane(
            modifier = Modifier
                .safeContentPadding()
                .background(Color.Red)
        ) {
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) {
                Button(
                    modifier = Modifier
                        .wrapContentSize(),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting)
                        }
                    }
                ) {
                    Text("Show supporting pane")
                }
            } else {
                Text("Supporting pane is shown")
            }
        }
    },
    supportingPane = {
        AnimatedPane(modifier = Modifier.safeContentPadding()) {
            Text("Supporting pane")
        }
    }
)

창 컴포저블 추출

SupportingPaneScaffold의 개별 창을 자체 컴포저블로 추출하여 재사용 가능하고 테스트 가능하도록 합니다. 기본 애니메이션을 사용하려면 ThreePaneScaffoldScope를 사용하여 AnimatedPane에 액세스합니다.

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.MainPane(
    shouldShowSupportingPaneButton: Boolean,
    onNavigateToSupportingPane: () -> Unit,
    modifier: Modifier = Modifier,
) {
    AnimatedPane(
        modifier = modifier.safeContentPadding()
    ) {
        // Main pane content
        if (shouldShowSupportingPaneButton) {
            Button(onClick = onNavigateToSupportingPane) {
                Text("Show supporting pane")
            }
        } else {
            Text("Supporting pane is shown")
        }
    }
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.SupportingPane(
    modifier: Modifier = Modifier,
) {
    AnimatedPane(modifier = modifier.safeContentPadding()) {
        // Supporting pane content
        Text("This is the supporting pane")
    }
}

창을 컴포저블로 추출하면 SupportingPaneScaffold 사용이 간소화됩니다 (다음을 이전 섹션의 스캐폴드 전체 구현과 비교하세요).

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane() },
)

스캐폴드의 특정 측면을 더 세부적으로 제어해야 하는 경우 NavigableSupportingPaneScaffold 대신 SupportingPaneScaffold를 사용하는 것이 좋습니다. PaneScaffoldDirectiveThreePaneScaffoldValue 또는 ThreePaneScaffoldState를 별도로 허용합니다. 이러한 유연성을 통해 창 간격에 관한 맞춤 로직을 구현하고 동시에 표시해야 하는 창 수를 결정할 수 있습니다. ThreePaneScaffoldPredictiveBackHandler를 추가하여 뒤로 탐색 예측 지원을 사용 설정할 수도 있습니다.

ThreePaneScaffoldPredictiveBackHandler 추가

스캐폴드 탐색기 인스턴스를 사용하는 뒤로 탐색 예측 핸들러를 연결하고 backBehavior를 지정합니다. 이를 통해 뒤로 탐색 중에 백 스택에서 대상이 팝되는 방식을 결정합니다. 그런 다음 scaffoldDirectivescaffoldStateSupportingPaneScaffold에 전달합니다. ThreePaneScaffoldState를 허용하는 오버로드를 사용하여 scaffoldNavigator.scaffoldState를 전달합니다.

SupportingPaneScaffold 내에서 기본 창과 지원 창을 정의합니다. 기본 창 애니메이션에는 AnimatedPane를 사용합니다.

이 단계를 구현하면 코드가 다음과 같이 표시됩니다.

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

ThreePaneScaffoldPredictiveBackHandler(
    navigator = scaffoldNavigator,
    backBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange
)

SupportingPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane() },
)