ניהול גרסאות של אריחים

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

  • ספריות שקשורות לרכיבי Jetpack Tile: הספריות האלה (כולל Wear Tiles ו-Wear ProtoLayout) מוטמעות באפליקציה, ואתם, כמפתחים, שולטים בגרסאות שלהן. האפליקציה משתמשת בספריות האלה כדי ליצור אובייקט TileBuilder.Tile (מבנה הנתונים שמייצג את הרכיב) בתגובה לקריאה של המערכת ל-onTileRequest().
  • ProtoLayout Renderer: רכיב המערכת הזה אחראי לעיבוד של אובייקט Tile בתצוגה ולטיפול באינטראקציות של המשתמשים. מפתח האפליקציה לא שולט בגרסה של רכיב העיבוד, והיא יכולה להשתנות בין מכשירים, גם אם יש להם חומרה זהה.

המראה או ההתנהגות של משבצת יכולים להשתנות בהתאם לגרסאות של ספריית Jetpack Tiles של האפליקציה ולגרסה של ProtoLayout Renderer במכשיר של המשתמש. לדוגמה, יכול להיות שמכשיר אחד תומך בסיבוב או בהצגת נתוני דופק, ומכשיר אחר לא.

במאמר הזה מוסבר איך לוודא שהאפליקציה תואמת לגרסאות שונות של ספריית Tiles ושל ProtoLayout Renderer, ואיך לבצע מיגרציה לגרסאות גבוהות יותר של ספריית Jetpack.

שיקולי תאימות

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

זיהוי היכולות של כלי הרינדור

אתם יכולים לשנות באופן דינמי את הפריסה של המשבצת בהתאם לתכונות שזמינות במכשיר מסוים.

זיהוי גרסת הרכיב לעיבוד

  • משתמשים בשיטה getRendererSchemaVersion() של האובייקט DeviceParameters שמועבר לשיטה onTileRequest(). השיטה הזו מחזירה את מספרי הגרסה הראשית והמשנית של ProtoLayoutRenderer במכשיר.
  • לאחר מכן תוכלו להשתמש בלוגיקה מותנית בהטמעה של onTileRequest() כדי להתאים את העיצוב או את ההתנהגות של ה-Tile על סמך הגרסה של רכיב הרינדור שזוהה.

ההערה @RequiresSchemaVersion

  • ההערה @RequiresSchemaVersion בשיטות של ProtoLayout מציינת את גרסת הסכימה המינימלית של רכיב ה-Renderer שנדרשת כדי שהשיטה תפעל כמו שמתואר במסמכים (דוגמה).
    • התקשרות לשיטה שדורשת גרסה גבוהה יותר של רכיב הרינדור מזו שזמינה במכשיר לא תגרום לקריסת האפליקציה, אבל היא עלולה לגרום לכך שהתוכן לא יוצג או שהתכונה תתעלם מהשיטה.

דוגמה לזיהוי גרסה

val rendererVersion = requestParams.deviceConfiguration.rendererSchemaVersion

val arcElement =
    // DashedArcLine has the annotation @RequiresSchemaVersion(major = 1, minor = 500)
    // and so is supported by renderer versions 1.500 and greater
    if (
        rendererVersion.major > 1 ||
        (rendererVersion.major == 1 && rendererVersion.minor >= 500)
    ) {
        // Use DashedArcLine if the renderer supports it …
        DashedArcLine.Builder()
            .setLength(degrees(270f))
            .setThickness(8f)
            .setLinePattern(
                LayoutElementBuilders.DashedLinePattern.Builder()
                    .setGapSize(8f)
                    .setGapInterval(10f)
                    .build()
            )
            .build()
    } else {
        // … otherwise use ArcLine.
        ArcLine.Builder().setLength(degrees(270f)).setThickness(dp(8f)).build()
    }

הוספת חלופות

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

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

val lottieImage =
    ResourceBuilders.ImageResource.Builder()
        .setAndroidLottieResourceByResId(
            ResourceBuilders.AndroidLottieResourceByResId.Builder(R.raw.lottie)
                .setStartTrigger(createOnVisibleTrigger())
                .build()
        )
        // Fallback if lottie is not supported
        .setAndroidResourceByResId(
            ResourceBuilders.AndroidImageResourceByResId.Builder()
                .setResourceId(R.drawable.lottie_fallback)
                .build()
        )
        .build()

בדיקה עם גרסאות שונות של רכיב ה-Renderer

כדי לבדוק את הרכיבים שלכם מול גרסאות שונות של כלי הרינדור, צריך לפרוס אותם לגרסאות שונות של האמולטור של Wear OS. (במכשירים פיזיים, עדכוני ProtoLayout Renderer מועברים דרך חנות Play או עדכוני מערכת. אי אפשר לכפות התקנה של גרסה ספציפית של רכיב הרינדור.)

תכונת התצוגה המקדימה של רכיבי Tile ב-Android Studio משתמשת במעבד רינדור שמוטמע בספריית Jetpack ProtoLayout שהקוד שלכם תלוי בה. לכן, גישה נוספת היא להסתמך על גרסאות שונות של ספריית Jetpack כשבודקים רכיבי Tile.

העברה ל-Tiles 1.5 / ProtoLayout 1.3 (Material 3 Expressive)

כדאי לעדכן את הספריות של רכיבי ה-Tile ב-Jetpack כדי ליהנות מהשיפורים האחרונים, כולל שינויים בממשק המשתמש שמאפשרים שילוב חלק של רכיבי ה-Tile במערכת.

בגרסאות Jetpack Tiles 1.5 ו-Jetpack ProtoLayout 1.3 יש כמה שיפורים ושינויים חשובים. למשל:

  • ממשק API שדומה ל-Compose לתיאור ממשק המשתמש.
  • רכיבי Material 3 Expressive, כולל לחצן קצה שצמוד לתחתית ותמיכה באפקטים חזותיים משופרים: אנימציות Lottie, סוגים נוספים של מעברי צבע וסגנונות חדשים של קווי קשת. ‫- הערה: אפשר להשתמש בחלק מהתכונות האלה גם בלי לבצע מיגרציה ל-API החדש.

המלצות

  • מעבירים את כל האריחים בו-זמנית. מומלץ להימנע משימוש בגרסאות שונות של רכיבי Tiles באפליקציה. רכיבי Material 3 נמצאים בארטיפקט נפרד (androidx.wear.protolayout:protolayout-material3), ולכן מבחינה טכנית אפשר להשתמש ברכיבי Tiles של M2.5 ו-M3 באותה אפליקציה. עם זאת, אנחנו ממליצים מאוד להימנע מהגישה הזו, אלא אם היא הכרחית (לדוגמה, אם באפליקציה יש מספר גדול של רכיבי Tiles שלא ניתן להעביר את כולם בבת אחת).
  • הטמעת ההנחיות לגבי חוויית המשתמש של Tiles. העיצובים בדוגמאות הקיימות יכולים לשמש כנקודות התחלה לעיצובים שלכם, כי האריחים הם מובנים מאוד ומבוססים על תבניות.
  • כדאי לבצע בדיקות במגוון גדלים של מסכים וגופנים. המשבצות מכילות בדרך כלל הרבה מידע, ולכן הטקסט (במיוחד כשהוא מופיע בלחצנים) עלול לגלוש או להיחתך. כדי לצמצם את הסיכון הזה, מומלץ להשתמש ברכיבים מוכנים מראש ולהימנע מהתאמות אישיות נרחבות. מומלץ לבצע בדיקה באמצעות התכונה לתצוגה מקדימה של כרטיסי מידע ב-Android Studio וגם בכמה מכשירים אמיתיים.

תהליך ההעברה

עדכון יחסי תלות

קודם צריך לעדכן את קובץ build.gradle.kts. מעדכנים את הגרסאות ומשנים את התלות protolayout-material ל-protolayout-material3, כמו שמוצג כאן:

// In build.gradle.kts

//val tilesVersion = "1.4.1"
//val protoLayoutVersion = "1.2.1"

// Use these versions for M3.
val tilesVersion = "1.5.0-rc01"
val protoLayoutVersion = "1.3.0-rc01"

 dependencies {
     // Use to implement support for wear tiles
     implementation("androidx.wear.tiles:tiles:$tilesVersion")

     // Use to utilize standard components and layouts in your tiles
     implementation("androidx.wear.protolayout:protolayout:$protoLayoutVersion")

     // Use to utilize components and layouts with Material Design in your tiles
     // implementation("androidx.wear.protolayout:protolayout-material:$protoLayoutVersion")
     implementation("androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion")

     // Use to include dynamic expressions in your tiles
     implementation("androidx.wear.protolayout:protolayout-expression:$protoLayoutVersion")

     // Use to preview wear tiles in your own app
     debugImplementation("androidx.wear.tiles:tiles-renderer:$tilesVersion")

     // Use to fetch tiles from a tile provider in your tests
     testImplementation("androidx.wear.tiles:tiles-testing:$tilesVersion")
 }

השירות TileService נשאר ללא שינוי

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

החריג העיקרי הוא מעקב אחר פעילות במשבצות: אם האפליקציה שלכם משתמשת ב-onTileEnterEvent() או ב-onTileLeaveEvent(), אתם צריכים לבצע מיגרציה ל-onRecentInteractionEventsAsync(). החל מ-API 36, האירועים האלה יצורפו לחבילות.

התאמת הקוד ליצירת פריסה

ב-ProtoLayout 1.2‏ (M2.5), השיטה onTileRequest() מחזירה TileBuilders.Tile. האובייקט הזה הכיל רכיבים שונים, כולל TimelineBuilders.Timeline, שבתורו הכיל את LayoutElement עם תיאור של ממשק המשתמש של האריח.

ב-ProtoLayout 1.3 ‏ (M3), מבנה הנתונים והזרימה הכלליים לא השתנו, אבל עכשיו LayoutElement נוצר באמצעות גישה בהשראת Compose עם פריסה שמבוססת על משבצות מוגדרות, שהן (מלמעלה למטה) titleSlot (אופציונלי; בדרך כלל לכותרת ראשית), mainSlot (חובה; לתוכן הליבה) ו-bottomSlot (אופציונלי; לרוב לפעולות כמו לחצן קצה או מידע נוסף כמו טקסט קצר). הפריסה הזו נוצרת על ידי הפונקציה primaryLayout().

הפריסה של משבצת שבה מוצגים mainSlot,‏ titleSlot ו-bottomSlot
איור 1.: משבצות של כרטיס מידע.
השוואה בין פונקציות הפריסה M2.5 ו-M3

M2.5

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters
) =
    PrimaryLayout.Builder(deviceConfiguration)
        .setResponsiveContentInsetEnabled(true)
        .setContent(
            Text.Builder(context, "Hello World!")
                .setTypography(Typography.TYPOGRAPHY_BODY1)
                .setColor(argb(0xFFFFFFFF.toInt()))
                .build()
        )
        .build()

M3

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
) =
    materialScope(context, deviceConfiguration) {
        primaryLayout(mainSlot = { text("Hello, World!".layoutString) })
    }

כדי להדגיש את ההבדלים העיקריים:

  1. הסרת כלי Builders. דפוס הבנייה המסורתי לרכיבי ממשק משתמש של Material3 מוחלף בתחביר יותר הצהרתי, בהשראת Compose. (רכיבים שאינם ממשק משתמש, כמו String/Color/Modifiers, מקבלים גם הם עטיפות חדשות של Kotlin).
  2. פונקציות סטנדרטיות של אתחול ופריסה. פריסות M3 מסתמכות על פונקציות אתחול ומבנה סטנדרטיות: materialScope() ו-primaryLayout(). הפונקציות האלה הן חובה, והן מאתחלות את סביבת M3 (עיצוב, היקף הרכיב באמצעות materialScope) ומגדירות את פריסת המשבצות הראשית (באמצעות primaryLayout). צריך לקרוא לכל אחת מהן בדיוק פעם אחת לכל פריסה.

קביעת עיצוב

צבע

תכונה בולטת של Material 3 Expressive היא 'ערכות נושא דינמיות': משבצות שמפעילות את התכונה הזו (מופעלת כברירת מחדל) יוצגו בערכת נושא שסופקה על ידי המערכת (הזמינות תלויה במכשיר ובהגדרה של המשתמש).

שינוי נוסף ב-M3 הוא הרחבה של מספר טוקני הצבע, שעלה מ-4 ל-29. אפשר למצוא את טוקני הצבע החדשים בכיתה ColorScheme.

טיפוגרפיה

בדומה ל-M2.5, ‏ M3 מסתמך במידה רבה על קבועים מוגדרים מראש של גודל הגופן – לא מומלץ לציין ישירות את גודל הגופן. הקבועים האלה נמצאים במחלקה Typography ומציעים מגוון רחב יותר של אפשרויות הבעה.

פרטים מלאים זמינים במסמכי התיעוד בנושא טיפוגרפיה.

צורה

רוב הרכיבים של M3 יכולים להשתנות לפי המאפיין של הצורה וגם לפי הצבע.

textButtonmainSlot) בצורה full:

אריח עם צורה מלאה (פינות מעוגלות יותר)
איור 2.: אריח עם צורה מלאה

אותו רכיב textButton עם צורה small:

משבצת בצורת 'קטן' (פינות פחות מעוגלות)
איור 3.: אריח בצורת 'קטן'

רכיבים

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

העיקרון הזה חל על פריסת ה-root. בגרסה M2.5, הערך היה PrimaryLayout או EdgeContentLayout. ב-M3, אחרי שנוצר MaterialScope ברמה העליונה, מתבצעת קריאה לפונקציה primaryLayout(). הפונקציה מחזירה את פריסת הבסיס ישירות (אין צורך ב-Builders) והיא מקבלת LayoutElements למספר 'חריצים', כמו titleSlot, mainSlot ו-bottomSlot. אפשר למלא את המשבצות האלה ברכיבי ממשק משתמש קונקרטיים – כמו אלה שמוחזרים על ידי text()‎,‏ button()‎ או card()‎ – או במבני פריסה, כמו Row או Column מתוך LayoutElementBuilders.

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

M2.5 M3
רכיבים אינטראקטיביים
Button או Chip
טקסט
Text text()
אינדיקטורים של התקדמות
CircularProgressIndicator circularProgressIndicator() או segmentedCircularProgressIndicator()
פריסה
PrimaryLayout או EdgeContentLayout primaryLayout()
buttonGroup()
תמונות
icon(), ‏ avatarImage() או backgroundImage()

גורמי שינוי

ב-M3, ‏ Modifiers, שמשמשים לעיצוב או להוספה של רכיב, דומים יותר ל-Compose. השינוי הזה יכול לצמצם את הקוד הסטנדרטי על ידי בנייה אוטומטית של הסוגים הפנימיים המתאימים. (השינוי הזה לא קשור לשימוש ברכיבי ממשק משתמש של M3. אם צריך, אפשר להשתמש במגדירי סגנון של Builder מ-ProtoLayout 1.2 עם רכיבי ממשק משתמש של M3, ולהיפך).

M2.5

// A Builder-style modifier to set the opacity of an element to 0.5
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

M3

// The equivalent Compose-like modifier is much simpler
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

אפשר ליצור משנים באמצעות סגנון API, ואפשר גם להשתמש בפונקציית התוסף toProtoLayoutModifiers() כדי להמיר LayoutModifier ל-ModifiersBuilders.Modifier.

פונקציות עזר

למרות ש-ProtoLayout 1.3 מאפשר להשתמש ב-API בהשראת Compose כדי להגדיר הרבה רכיבי ממשק משתמש, רכיבי פריסה בסיסיים כמו שורות ועמודות מ-LayoutElementBuilders ממשיכים להשתמש בתבנית builder. כדי לגשר על הפער הסגנוני הזה ולקדם עקביות עם ממשקי ה-API החדשים של רכיבי M3, מומלץ להשתמש בפונקציות עזר.

ללא עזרה

primaryLayout(
    mainSlot = {
        LayoutElementBuilders.Column.Builder()
            .setWidth(expand())
            .setHeight(expand())
            .addContent(text("A".layoutString))
            .addContent(text("B".layoutString))
            .addContent(text("C".layoutString))
            .build()
    }
)

עם עוזרים

// Function literal with receiver helper function
fun column(builder: Column.Builder.() -> Unit) =
    Column.Builder().apply(builder).build()

primaryLayout(
    mainSlot = {
        column {
            setWidth(expand())
            setHeight(expand())
            addContent(text("A".layoutString))
            addContent(text("B".layoutString))
            addContent(text("C".layoutString))
        }
    }
)

מעבר לגרסה Tiles 1.2 / ProtoLayout 1.0

החל מגרסה 1.2, רוב ממשקי ה-API של פריסת הרכיבים נמצאים במרחב השמות androidx.wear.protolayout. כדי להשתמש בממשקי ה-API העדכניים, צריך לבצע את שלבי ההעברה הבאים בקוד.

עדכון יחסי תלות

בקובץ הבנייה של מודול האפליקציה, מבצעים את השינויים הבאים:

Groovy

  // Remove
  implementation 'androidx.wear.tiles:tiles-material:version'

  // Include additional dependencies
  implementation "androidx.wear.protolayout:protolayout:1.3.0"
  implementation "androidx.wear.protolayout:protolayout-material:1.3.0"
  implementation "androidx.wear.protolayout:protolayout-expression:1.3.0"

  // Update
  implementation "androidx.wear.tiles:tiles:1.5.0"

Kotlin

  // Remove
  implementation("androidx.wear.tiles:tiles-material:version")

  // Include additional dependencies
  implementation("androidx.wear.protolayout:protolayout:1.3.0")
  implementation("androidx.wear.protolayout:protolayout-material:1.3.0")
  implementation("androidx.wear.protolayout:protolayout-expression:1.3.0")

  // Update
  implementation("androidx.wear.tiles:tiles:1.5.0")

עדכון מרחבי שמות

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

  1. החלפת כל הייבוא של androidx.wear.tiles.material.* ב-androidx.wear.protolayout.material.*. צריך להשלים את השלב הזה גם עבור הספרייה androidx.wear.tiles.material.layouts.
  2. מחליפים את רוב הייבוא של androidx.wear.tiles.* ב-androidx.wear.protolayout.*.

    הייבוא של androidx.wear.tiles.EventBuilders,‏ androidx.wear.tiles.RequestBuilders,‏ androidx.wear.tiles.TileBuilders ו-androidx.wear.tiles.TileService צריך להישאר ללא שינוי.

  3. שינוי השם של כמה שיטות שהוצאו משימוש במחלקות TileService ו-TileBuilder:

    1. TileBuilders: getTimeline() עד getTileTimeline(), וגם setTimeline() עד setTileTimeline()
    2. TileService: onResourcesRequest() עד onTileResourcesRequest()
    3. RequestBuilders.TileRequest: getDeviceParameters() עד getDeviceConfiguration(),‏ setDeviceParameters() עד setDeviceConfiguration(),‏ getState() עד getCurrentState() ו-setState() עד setCurrentState()