תצוגה מקדימה של ממשק המשתמש ב-Compose ל-Wear OS

בעזרת התכונה 'תצוגה מקדימה של Compose' ב-Android Studio, אפשר לבדוק ולאמת את רכיבי ה-Composable של Wear OS בגדלים שונים של מסכי שעונים, במסגרות עגולות ובגופנים בגדלים שונים ישירות בסביבת הפיתוח המשולבת (IDE) – בלי לפרוס את האפליקציה בשעון פיזי או באמולטור.

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


הגדרת יחסי תלות בתצוגה המקדימה

כדי להשתמש בהערות לתצוגה מקדימה של Wear OS Compose ובהגדרות מכשיר, מוסיפים את יחסי התלות הבאים לקובץ build.gradle.kts של המודול:

dependencies {
    // Provides @WearPreview* multipreview annotations
    // (such as @WearPreviewDevices and @WearPreviewFontScales)
    implementation("androidx.wear.compose:compose-ui-tooling:1.7.0")

    // Provides WearDevices constants
    // (such as WearDevices.SMALL_ROUND and WearDevices.LARGE_ROUND)
    implementation("androidx.wear:wear-tooling-preview:1.0.0")

    // Standard Compose preview support and interactive/animation inspection
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

בחירת התצוגה המקדימה: מסכים לעומת רכיבים

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

תצוגה מקדימה במסך מלא (AppScaffold + ScreenScaffold)

כשמציגים תצוגה מקדימה של מסך שלם, תמיד צריך להוסיף את ה-composable של המסך גם ל-AppScaffold וגם ל-ScreenScaffold באמצעות הערה של תצוגה מקדימה של מכשיר Wear. כך מוצג המסך העגול של השעון, ומוודאים ש:

  • הסמל TimeText מוצג בקצה המעוקל העליון של פני השעון.
  • ScrollIndicator מופיע לאורך המסגרת הימנית.
  • האלמנט EdgeButton ממוקם בצורה נכונה ונחתך בקימור התחתון.
  • הוספת מרווחים מסביב לתוכן וחיתוך מסך עגול משקפים בצורה מדויקת את החומרה של שעון אמיתי.
@WearPreviewDevices
@Composable
fun WorkoutScreenPreview() {
    MaterialTheme {
        // AppScaffold provides the top-level TimeText overlay
        AppScaffold {
            // WorkoutScreen contains its own ScreenScaffold and content
            WorkoutScreen(
                heartRate = 142,
                elapsedTime = "12:45"
            )
        }
    }
}
‫WorkoutScreenPreview שמעובד ב-WearDevices.SMALL_ROUND

קטן ועגול (192x192dp)

‫WorkoutScreenPreview שעבר רינדור ב-WearDevices.LARGE_ROUND

Large Round (227x227dp)

תצוגה מקדימה של רכיבים מבודדים

כשמציגים תצוגה מקדימה של רכיבים ספציפיים – כמו Card, Button או צ'יפ סטטוס בהתאמה אישית – צריך להשמיט את הפרמטר device ולהשתמש בפרמטר @Preview רגיל עם רקע כהה. כך מוודאים שהצבעים והניגודיות של Wear Material 3 מוצגים בצורה מדויקת בלי להציג את התצוגה המעגלית המלאה של השעון:

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
תצוגה מקדימה של רכיב מבודד HeartRateCardPreview ללא מסגרת שעון

תצוגה מקדימה של רכיב מבודד (ללא מסגרת המכשיר).


הערות מובנות בתצוגה המקדימה

חבילת androidx.wear.compose.ui.tooling.preview מספקת הערות מובנות שמגדירות באופן אוטומטי רקעים כהים (backgroundColor = 0xFF000000, showBackground = true) ומידות של מכשיר שעון עגול:

הערה מה מוצג מתי להשתמש?
@WearPreviewSmallRound תצוגה מקדימה אחת ב-WearDevices.SMALL_ROUND (192x192dp). חזרה מהירה על גודל התצוגה העגולה המוגבל ביותר.
@WearPreviewLargeRound תצוגה מקדימה אחת בWearDevices.LARGE_ROUND (227x227dp). בדיקת צפיפות הפריסה והמרווחים הנוספים בשעונים גדולים יותר.
@WearPreviewDevices 2 תצוגות מקדימות: SMALL_ROUND ו-LARGE_ROUND. בדיקה רגילה של שימוש בכמה מכשירים לכל קומפוזיציה של מסך.
@WearPreviewFontScales 6 תצוגות מקדימות ב-SMALL_ROUND בכל קנה מידה של גופן Wear: קטן (0.94f), רגיל (1.0f), בינוני (1.06f), גדול (1.12f), גדול יותר (1.18f) והגדול ביותר (1.24f). בודקים את גלישת הטקסט, את השימוש בסימן שלוש הנקודות ואת הרחבת הגובה של הלחצן.

אפשר להשתמש בפונקציות @WearPreviewDevices ו-@WearPreviewFontScales באותה פונקציית תצוגה מקדימה כדי ליצור מטריצת בדיקה מקיפה:

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun MessageDetailScreenPreview() {
    MaterialTheme {
        AppScaffold {
            MessageDetailScreen(
                sender = "Alex",
                body = "Running 5 mins late!"
            )
        }
    }
}

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

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

קבועים WearDevices זמינים ומפרטי חומרה בהתאמה אישית

אובייקט androidx.wear.tooling.preview.devices.WearDevices מספק מזהי מכשירים סטנדרטיים:

  • WearDevices.SMALL_ROUND ("id:wearos_small_round", ‏ 192x192dp)
  • WearDevices.LARGE_ROUND ("id:wearos_large_round", ‏ 227x227dp)

כדי לראות תצוגה מקדימה במסכים עגולים גדולים במיוחד (כמו שעונים בגודל 44 מ"מ עד 45 מ"מ או דגמי Ultra ברזולוציה של 240x240dp), מעבירים מחרוזת spec: מותאמת אישית לפרמטר device:

@Preview(
    name = "XL Round Watch (240dp)",
    device = "spec:width=240dp,height=240dp,dpi=320,isRound=true",
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun WorkoutScreenXlPreview() {
    MaterialTheme {
        AppScaffold {
            WorkoutScreen(heartRate = 142, elapsedTime = "12:45")
        }
    }
}

יצירת הערה מותאמת אישית לתצוגה מקדימה מרובה

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

@Preview(
    name = "1. Standard Large Round",
    group = "Layout extremes",
    device = WearDevices.LARGE_ROUND,
    backgroundColor = 0xFF000000,
    showBackground = true
)
@Preview(
    name = "2. Extreme Small Round (Largest Font + German)",
    group = "Layout extremes",
    device = WearDevices.SMALL_ROUND,
    fontScale = 1.24f,
    locale = "de-rDE",
    backgroundColor = 0xFF000000,
    showBackground = true
)
annotation class WearPreviewExtremes
תצוגה מקדימה של סמל גדול ועגול רגיל

1. Standard Large Round

עיגול קטן במיוחד עם קנה מידה גדול ביותר של גופן

2. קיצוני קטן עגול (הגופן הגדול ביותר + גרמנית)


תצוגה מקדימה של עמודות בגלילה (TransformingLazyColumn)

כברירת מחדל, TransformingLazyColumn מאותחל עם הפריט הראשון שלו (index = 0) שמוצמד לחלק העליון של המסך. עם זאת, ב-Wear OS, הגובה של הפריטים משתנה והפינות המעוגלות שלהם (SurfaceTransformation) משתנות כשהם מתקרבים לקצוות המעוקלים העליונים והתחתונים של המסך, והסמל EdgeButton מופיע רק כשגוללים לתחתית.

כדי לראות תצוגה מקדימה של הרשימה כשגוללים אותה חלקית או עד הסוף:

שלב 1: מוסיפים את TransformingLazyColumnState Hoist לקומפוזיציה של המסך

מאפשרים לרכיב ה-Composable של המסך לקבל פרמטר TransformingLazyColumnState עם rememberTransformingLazyColumnState() כערך ברירת המחדל:

@Composable
fun InboxScreen(
    messages: List<Message>,
    columnState: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
) {
    val transformationSpec = rememberTransformationSpec()

    ScreenScaffold(
        scrollState = columnState,
        edgeButton = {
            EdgeButton(onClick = { /* Compose new */ }) {
                Text("New message")
            }
        }
    ) { contentPadding ->
        TransformingLazyColumn(
            state = columnState,
            contentPadding = contentPadding,
        ) {
            items(messages.size) { index ->
                Card(
                    onClick = {},
                    modifier = Modifier
                        .fillMaxWidth()
                        .transformedHeight(this, transformationSpec)
                        .minimumVerticalContentPadding(
                            CardDefaults.minimumVerticalListContentPadding
                        ),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text(messages[index].subject)
                }
            }
        }
    }
}

שלב 2: מעבירים את initialAnchorItemIndex ב-@Preview

rememberTransformingLazyColumnState מקבל שני פרמטרים אופציונליים של גלילה ראשונית:

  • initialAnchorItemIndex: Int: אם מגדירים אינדקס לא שלילי (לדוגמה, 3), הרשימה מאותחלת עם הפריט הזה במרכז אזור התצוגה של הצפייה.
  • initialAnchorItemScrollOffset: Int: היסט אופציונלי של הפיקסל שמוחל ביחס לפריט העוגן הממורכז.

אתם יכולים ליצור תצוגות מקדימות זו לצד זו שמציגות את המצבים למעלה, באמצע (אחרי גלילה) ולמטה (EdgeButton גלוי) של אותו מסך בדיוק:

@WearPreviewLargeRound
@Composable
fun InboxScreenTopPreview() {
    MaterialTheme {
        AppScaffold {
            // Default (-1): Pinned to top of list (index 0)
            InboxScreen(messages = sampleMessages)
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenScrolledMiddlePreview() {
    MaterialTheme {
        AppScaffold {
            // Centers item index 3 in the viewport, showing top/bottom item morphing
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = 3
                )
            )
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenBottomEdgeButtonPreview() {
    MaterialTheme {
        AppScaffold {
            // Anchors on the last item so the EdgeButton is visible at the bottom
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = sampleMessages.lastIndex
                )
            )
        }
    }
}
המסך של תיבת הדואר הנכנס הוצמד לחלק העליון של הרשימה

למעלה (ברירת מחדל -1)

בוצעה גלילה בתיבת הדואר הנכנס לאינדקס האמצעי 3

אמצע (initialAnchorItemIndex = 3)

מסך תיבת הדואר הנכנס שגולל לתחתית עם כפתור Edge מורחב

למטה (EdgeButton מורחב)

טיפ: אפשר גם ללחוץ על התחלת מצב אינטראקטיבי בכל @Preview ב-Android Studio כדי לגלול את TransformingLazyColumn בזמן אמת באמצעות העכבר או משטח המגע ולבדוק את SurfaceTransformation המורפינג, את EdgeButton האנימציות של הכניסה ואת ScrollIndicator התנועה בזמן אמת.

‫Guard ScrollIndicator במהלך צילום מסך בגלילה (LocalScrollCaptureInProgress)

כשמצלמים מסך בגלילה (צילומי מסך ארוכים) או כשמשתמשים בכלי לבדיקת צילומי מסך מרובי מסגרות, Compose מגדיר את TransformingLazyColumnLocalScrollCaptureInProgress.current ל-true בזמן הצילום והחיבור של כמה משבצות של אזור התצוגה באופן אנכי.

מכיוון שהאפליקציה ScreenScaffold לא מסתירה אוטומטית את scrollIndicator במהלך צילום מסך בגלילה, שכבת-העל של סרגל הגלילה הצף תופיע שוב ושוב בכל משבצת שחוברו יחד בצילום מסך ארוך, אלא אם תגדירו במפורש את !LocalScrollCaptureInProgress.current:

ScreenScaffold(
    scrollState = columnState,
    scrollIndicator = {
        if (!LocalScrollCaptureInProgress.current) {
            ScrollIndicator(state = columnState)
        }
    }
) { contentPadding ->
    // TransformingLazyColumn content...
    // ...
}