פיתוח פריסת חלונית תומכת

הפריסה של חלונית התמיכה מאפשרת למשתמש להתמקד בתוכן הראשי של האפליקציה, תוך הצגת מידע תומך רלוונטי. לדוגמה, בחלונית הראשית יכולים להופיע פרטים על סרט, ובחלונית התומכת יכולים להופיע סרטים דומים, סרטים של אותו במאי או יצירות שבהן מופיעים אותם שחקנים.

פרטים נוספים זמינים בהנחיות לגבי חלונית התמיכה של חומר 3.

הטמעת חלונית תמיכה באמצעות NavigableSupportingPaneScaffold

NavigableSupportingPaneScaffold הוא רכיב שקל להטמיע באמצעותו פריסה של חלונית תמיכה ב-Jetpack Compose. הוא עוטף את SupportingPaneScaffold ומוסיף ניווט מובנה וטיפול חזוי בלחצן 'הקודם'.

תומך של חלונית תומכת יכול לתמוך בעד שלוש חלוניות:

  • החלונית הראשית: כאן מוצג התוכן הראשי.
  • חלונית תמיכה: מספקת הקשר או כלים נוספים שקשורים לחלונית הראשית.
  • חלונית נוספת (אופציונלי): משמשת לתוכן משלים במקרה הצורך.

התבנית מתאימה את עצמה בהתאם לגודל החלון:

  • בחלונות גדולים, החלונית הראשית והחלונית התומכת מופיעות זו לצד זו.
  • בחלונות קטנים, רק חלונית אחת מוצגת בכל פעם, והיא משתנה בהתאם לניווט של המשתמשים.

    התוכן הראשי תופס את רוב המסך, לצד תוכן תומך.
    איור 1. תמיכה בפריסה של חלוניות.

הוספת יחסי תלות

NavigableSupportingPaneScaffold הוא חלק מספריית הפריסות המותאמות של Material 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'
  • אדפטיביות: אבני בניין ברמה נמוכה, כמו HingeInfo ו-Posture
  • adaptive-layout: פריסות מותאמות כמו ListDetailPaneScaffold ו-SupportingPaneScaffold
  • adaptive-navigation: רכיבים מורכבים לניווט בתוך חלוניות ובין חלוניות, וכן פריסות מותאמות שמאפשרות ניווט כברירת מחדל, כמו NavigableListDetailPaneScaffold ו-NavigableSupportingPaneScaffold

מוודאים שהפרויקט כולל את compose-material3-adaptive בגרסה 1.1.0-beta1 ואילך.

איך מביעים הסכמה לשימוש בתנועת החזרה החזוי

כדי להפעיל אנימציות של תנועת החזרה חזוי ב-Android 15 ואילך, צריך להביע הסכמה לתמיכה בתנועת החזרה חזוי. כדי להביע הסכמה, מוסיפים את הערך android:enableOnBackInvokedCallback="true" לתג <application> [או את הערך android:enableOnBackInvokedCallback="true" לתג <application> או לתגים <activity> הנפרדים] בקובץ AndroidManifest.xml.

אחרי שהאפליקציה שלכם מטרגטת ל-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() },
)

אם אתם רוצים יותר שליטה על היבטים ספציפיים של התבנית, כדאי להשתמש ב-SupportingPaneScaffold במקום ב-NavigableSupportingPaneScaffold. אפשר להזין את הערכים PaneScaffoldDirective ו-ThreePaneScaffoldValue או את הערכים ThreePaneScaffoldState בנפרד. הגמישות הזו מאפשרת לכם להטמיע לוגיקה מותאמת אישית למרווח בין החלונות ולקבוע כמה חלונות יוצגו בו-זמנית. אפשר גם להוסיף את הערך ThreePaneScaffoldPredictiveBackHandler כדי להפעיל תמיכה בחזור חזרה אוטומטי.

הוספה של ThreePaneScaffoldPredictiveBackHandler

מחברים את ה-handler לחיזוי תנועת החזרה, שמקבל מכונה של ניווט בסכימה ומציינים את backBehavior. ההגדרה הזו קובעת איך יעברו יעדים מהמקבץ הקודם בזמן ניווט לאחור. לאחר מכן מעבירים את scaffoldDirective ו-scaffoldState אל SupportingPaneScaffold. משתמשים בעלות יתר שמקבלת 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() },
)