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

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

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

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

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

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

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

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

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

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

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

ההערה @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)

כדאי לעדכן את הספריות של רכיבי Jetpack Tile כדי ליהנות מהשיפורים האחרונים, כולל שינויים בממשק המשתמש שמאפשרים שילוב חלק של רכיבי ה-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"
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.: אריח עם צורה מלאה

אותו לחצן טקסט עם צורה small:

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

רכיבים

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

העיקרון הזה חל גם על פריסת השורש. בגרסה 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

// 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 בהשראת Compose, רכיבי פריסה בסיסיים כמו rows ו-columns מ-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 של מודול האפליקציה, מבצעים את השינויים הבאים:

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