החלפת דפים באמצעות Compose for Wear OS

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

ב-Compose for Wear OS Material 3, ‏ HorizontalPagerScaffold ו-VerticalPagerScaffold מתאמים את פריסת ה-pager, ממקמים אוטומטית את מחוון הדף ומנהלים את המעברים באמצעות TimeText. כל דף עטוף ברכיב AnimatedPage שאפשר להוסיף לו, שמחיל אפקטים של שינוי גודל, שינוי צורה של פינות מעוגלות ושינוי צבע הרקע, כשעוברים בין דפים במסך העגול.

באנימציה הבאה אפשר לראות איך הדפים HorizontalPagerScaffold ו-AnimatedPage משנים את הגודל שלהם ומונפשים במהלך החלקה אופקית:

היררכיית Scaffold להחלפת דפים

כשמעצבים פריסות מסך עם חלוקה לדפים ב-Material 3 – בין אם מחליקים שמאלה או ימינה (HorizontalPager) או למעלה או למטה (VerticalPager) – משתמשים בהיררכיית הרכיבים הבאה מהמאגר החיצוני לתוכן הפנימי:

  1. AppScaffold: הקונטיינר החיצוני ביותר בבסיס האפליקציה (אפשר להשתמש רק באחד לכל אפליקציה). הוא מעגן את שכבת העל הגלובלית TimeText כך שהשעון יישאר קבוע בחלק העליון של המסך במהלך מעברים בין דפים ומחוות של החלקה לביטול.
  2. HorizontalPagerScaffold או VerticalPagerScaffold: ממוקם בתוך AppScaffold ברמת הדפדוף. הוא מתאם את המעברים בין TimeText לבין HorizontalPageIndicator או VerticalPageIndicator.
  3. HorizontalPager או VerticalPager: קונטיינר של רכיב החלפת הדפים הבסיסי שמנהל מחוות החלקה, פיזיקת הטלה וקליקים של חוגה להזנת נתונים באמצעות PagerState משותף.
  4. AnimatedPage: ממוקם בתוך פונקציית ה-lambda של תוכן הדף של רכיב ה-pager. הוא עוטף כל דף בנפרד כדי להחיל אנימציות מעבר של Material 3 (אפקטים של שינוי גודל ושל מסך חצי שקוף) על סמך ההיסט של הדף ב-PagerState.
  5. ScreenScaffold: התג הזה ממוקם בתוך התג AnimatedPage לכל דף בנפרד. מכיוון שכל דף יכול להכיל רשימה משלו עם אפשרות גלילה אנכית (כמו TransformingLazyColumn) או EdgeButton משלו, הוספת ScreenScaffold בתוך כל דף מבטיחה שסמני הגלילה האנכית, לחצני הקצה ו-contentPadding ישתייכו לדף הספציפי הזה ויפעלו בצורה חלקה יחד איתו.
AppScaffold (1 per app: anchors global TimeText)
 └── HorizontalPagerScaffold / VerticalPagerScaffold (manages PageIndicator)
      └── HorizontalPager / VerticalPager (manages PagerState & fling behavior)
           └── AnimatedPage (applies scaling & scrim transitions per page)
                └── ScreenScaffold (1 per page: ScrollIndicator & EdgeButton)
                     └── Page Content (Column or TransformingLazyColumn)

הטמעה של תצוגת דפים אופקית

כדי להטמיע רכיב pager אופקי ב-Material 3, צריך להוסיף את הרכיבים HorizontalPagerScaffold, HorizontalPager,‏ AnimatedPage ו-ScreenScaffold בתוך הרכיב AppScaffold של האפליקציה.

משתמשים ב-PagerScaffoldDefaults.snapWithSpringFlingBehavior בשביל flingBehavior כדי להחיל פיזיקה של תנועת קפיץ Material 3 והצמדה רספונסיבית של דפים (HighSnapPositionalThreshold). כברירת מחדל, HorizontalPager משבית את הגלילה הסיבובית של הדפים (rotaryScrollableBehavior = null), ומאפשר להשתמש בסיבוב של הכתר או המסגרת כדי לגלול ברשימות אנכיות (כמו TransformingLazyColumn) בתוך הדף הפעיל.

בדוגמה הבאה מוצגת הגדרה מלאה של HorizontalPagerScaffold:

@Composable
fun HorizontalPagerScaffoldSample(navigateBack: () -> Unit) {
    AppScaffold {
        val pagerState = rememberPagerState(pageCount = { 10 })

        HorizontalPagerScaffold(pagerState = pagerState) {
            HorizontalPager(
                state = pagerState,
                flingBehavior =
                    PagerScaffoldDefaults.snapWithSpringFlingBehavior(
                        state = pagerState
                    ),
            ) { page ->
                AnimatedPage(pageIndex = page, pagerState = pagerState) {
                    ScreenScaffold {
                        Column(
                            modifier = Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center,
                        ) {
                            Text(text = "Page #$page")
                            Spacer(modifier = Modifier.height(8.dp))
                            Text(text = "Swipe left and right")
                            if (page == 0) {
                                Spacer(modifier = Modifier.height(16.dp))
                                Button(onClick = navigateBack) { Text("Exit") }
                            }
                        }
                    }
                }
            }
        }
    }
}

אם דף בתוך רכיב ה-pager האופקי מכיל TransformingLazyColumn, צריך ליצור rememberTransformingLazyColumnState() נפרד בהיקף של הדף הזה ולהעביר אותו אל ScreenScaffold ו-TransformingLazyColumn הפנימיים:

AppScaffold {
    val pagerState = rememberPagerState(pageCount = { 10 })

    HorizontalPagerScaffold(pagerState = pagerState) {
        HorizontalPager(
            state = pagerState,
            flingBehavior =
                PagerScaffoldDefaults.snapWithSpringFlingBehavior(
                    state = pagerState
                ),
        ) { page ->
            AnimatedPage(pageIndex = page, pagerState = pagerState) {
                val columnState = rememberTransformingLazyColumnState()
                val transformationSpec = rememberTransformationSpec()

                ScreenScaffold(
                    scrollState = columnState,
                ) { contentPadding ->
                    TransformingLazyColumn(
                        state = columnState,
                        contentPadding = contentPadding,
                    ) {
                        item {
                            ListHeader(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .transformedHeight(this, transformationSpec)
                                    .minimumVerticalContentPadding(
                                        ListHeaderDefaults.minimumTopListContentPadding
                                    ),
                                transformation = SurfaceTransformation(transformationSpec),
                            ) {
                                Text(text = "Pager sample")
                            }
                        }
                        item {
                            Card(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .transformedHeight(this, transformationSpec)
                                    .minimumVerticalContentPadding(
                                        CardDefaults.minimumVerticalListContentPadding
                                    ),
                                transformation = SurfaceTransformation(transformationSpec),
                            ) {
                                if (page == 0) {
                                    Text(text = "Page #$page. Swipe right")
                                } else {
                                    Text(text = "Page #$page. Swipe left and right")
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

הטמעה של תצוגת דפים אנכית

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

בניגוד ל-HorizontalPager, ‏ VerticalPager מאפשרת הצמדה סיבובית של דפים כברירת מחדל (RotaryScrollableDefaults.snapBehavior(state)). מעבירים את PagerScaffoldDefaults.snapWithSpringFlingBehavior אל flingBehavior ועוטפים כל דף ב-AnimatedPage וב-ScreenScaffold:

@Composable
fun VerticalPagerScaffoldSample() {
    AppScaffold {
        val pagerState = rememberPagerState(pageCount = { 10 })

        VerticalPagerScaffold(pagerState = pagerState) {
            VerticalPager(
                state = pagerState,
                flingBehavior =
                    PagerScaffoldDefaults.snapWithSpringFlingBehavior(
                        state = pagerState
                    ),
            ) { page ->
                AnimatedPage(pageIndex = page, pagerState = pagerState) {
                    ScreenScaffold {
                        Column(
                            modifier = Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center,
                        ) {
                            Text(text = "Page #$page")
                            Spacer(modifier = Modifier.height(8.dp))
                            Text(text = "Swipe up and down")
                        }
                    }
                }
            }
        }
    }
}

התאמה אישית של התנהגות מכשיר הפייג'ר

אתם יכולים לשנות את מידת הרגישות של ההצמדה של HorizontalPagerScaffold ושל VerticalPagerScaffold בהתאם לצרכים של האינטראקציה עם האפליקציה.

שינוי מידת הרגישות של ההצמדה לאימונים

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

  • כדי להעביר את PagerDefaults.snapFlingBehavior עם maxFlingPages = 0 ו-snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, צריך לגרור בכוונה חלק גדול יותר של המסך כדי להעביר את הדף.
  • אם מפעילים את ההגדרה 'הצמדת דפים בסיבוב', צריך להגדיר את snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity ב-RotaryScrollableDefaults.snapBehavior כדי לדרוש סיבוב נוסף לפני ההצמדה לדף הבא.
@Composable
fun HorizontalPagerScaffoldWithLowSensitivitySample(navigateBack: () -> Unit) {
    AppScaffold {
        val pagerState = rememberPagerState(pageCount = { 3 })

        HorizontalPagerScaffold(pagerState = pagerState) {
            HorizontalPager(
                state = pagerState,
                flingBehavior =
                    PagerDefaults.snapFlingBehavior(
                        state = pagerState,
                        maxFlingPages = 0,
                        snapPositionalThreshold =
                            PagerScaffoldDefaults.LowSnapPositionalThreshold,
                    ),
                rotaryScrollableBehavior =
                    RotaryScrollableDefaults.snapBehavior(
                        pagerState = pagerState,
                        snapSensitivity =
                            RotaryScrollableDefaults.LowSnapSensitivity,
                    ),
            ) { page ->
                AnimatedPage(pageIndex = page, pagerState = pagerState) {
                    ScreenScaffold {
                        // Page content
                    }
                }
            }
        }
    }
}

דוגמאות מלאות להטמעה זמינות ב-Pager.kt במאגר android/snippets וב-PagerScaffoldSample.kt במאגר AndroidX.