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

במכשירי 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 שלא ניתן להעביר את כולם בבת אחת).
  • הטמעת ההנחיות לגבי חוויית המשתמש של כרטיסי מידע. העיצובים בדוגמאות הקיימות יכולים לשמש כנקודות התחלה לעיצובים שלכם, כי הם מובנים ומבוססים על תבניות.
  • כדאי לבצע בדיקות במגוון גדלים של מסכים וגופנים. המשבצות לרוב מכילות הרבה מידע, ולכן הטקסט (במיוחד כשהוא מופיע בלחצנים) עלול לגלוש או להיחתך. כדי לצמצם את הסיכון הזה, כדאי להשתמש ברכיבים מוכנים מראש ולהימנע מהתאמה אישית נרחבת. מומלץ לבדוק את כרטיס המידע באמצעות התכונה של 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"
val protoLayoutVersion = "1.3.0"

 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)
                .build()
        )
        .build()

M3

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

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

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

ניהול עיצובים

ב-Material 3 יש כמה שינויים בעיצוב, כולל צבע דינמי ומערך מורחב של אפשרויות טיפוגרפיה וצורה.

צבע

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

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

טיפוגרפיה

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

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

צורה

ברוב רכיבי M3 יש וריאציות בצורה ובצבע.

textButtonmainSlot) בצורה full:

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

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

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

רכיבים

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

העיקרון הזה חל גם על פריסת השורש. בגרסה M2.5, הערך היה PrimaryLayout או EdgeContentLayout. ב-M3, אחרי שיוצרים MaterialScope ברמה העליונה, קוראים לפונקציה primaryLayout(). הפונקציה הזו מחזירה את פריסת השורש ישירות – לא צריך בנאים – והיא מקבלת 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 UI. אם צריך, אפשר להשתמש במאפייני שינוי בסגנון builder מ-ProtoLayout 1.2 עם רכיבי M3 UI, ולהיפך).

M2.5

// Uses Builder-style modifier to set opacity
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

M3

// Uses Compose-like modifiers to set opacity
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

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

פונקציות עזר

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

ללא עזרה

primaryLayout(
    mainSlot = {
        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 העדכניים, צריך לבצע את שלבי ההעברה הבאים בקוד.

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

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

מגניב

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

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

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

Kotlin

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

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

  // Update
  implementation("androidx.wear.tiles:tiles:1.6.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()