יצירת פריסות ווידג'ט גמישות

איך עושים את זה ב-Compose
‫Jetpack Compose היא ערכת הכלים המומלצת לפיתוח ממשק משתמש ב-Android. איך יוצרים ווידג'טים באמצעות ממשקי API בסגנון Compose.

בדף הזה מתוארים שיפורים בגודל הווידג'טים וגמישות רבה יותר שהוצגו ב-Android 12 (רמת API‏ 31). בנוסף, מוסבר איך לקבוע את גודל הווידג'ט.

שימוש בממשקי API משופרים לגדלים ולפריסות של ווידג'טים

החל מ-Android 12 (רמת API‏ 31), אפשר לספק מאפייני גודל מדויקים יותר ופריסות גמישות. כדי לעשות את זה, פועלים לפי ההוראות בקטעים הבאים:

  1. מגדירים אילוצים נוספים לגודל הווידג'ט.

  2. פריסות רספונסיביות או פריסות מדויקות.

בגרסאות קודמות של Android, אפשר לקבל את טווחי הגודל של ווידג'ט באמצעות התוספות OPTION_APPWIDGET_MIN_WIDTH,‏ OPTION_APPWIDGET_MIN_HEIGHT,‏ OPTION_APPWIDGET_MAX_WIDTH ו-OPTION_APPWIDGET_MAX_HEIGHT, ואז להעריך את גודל הווידג'ט, אבל הלוגיקה הזו לא פועלת בכל המצבים. לווידג'טים שמטרגטים את Android 12 ואילך, מומלץ לספק פריסות רספונסיביות או פריסות מדויקות.

הגדרת אילוצים נוספים על גודל הווידג'ט

ב-Android 12 נוספו ממשקי API שמאפשרים לוודא שהגודל של הווידג'ט מותאם בצורה אמינה יותר למכשירים שונים עם מסכים בגדלים שונים.

בנוסף למאפיינים הקיימים minWidth,‏ minHeight,‏ minResizeWidth ו-minResizeHeight, צריך להשתמש במאפיינים החדשים הבאים של appwidget-provider:

  • ‫targetCellWidth ו-targetCellHeight: מגדירים את גודל היעד של הווידג'ט במונחים של תאי רשת במרכז האפליקציות. אם המאפיינים האלה מוגדרים, המערכת משתמשת בהם במקום במאפיינים minWidth או minHeight.

  • ‫maxResizeWidth ו-maxResizeHeight: מגדירים את הגודל המקסימלי שמרכז האפליקציות מאפשר למשתמש לשנות את גודל הווידג'ט אליו.

ה-XML הבא מראה איך להשתמש במאפייני הגודל.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

פריסות רספונסיביות

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

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

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

override fun onUpdate(
    /* ... */
) {
    val smallView = RemoteViews(
        /* ... */
    )
    val tallView = RemoteViews(
        /* ... */
    )
    val wideView = RemoteViews(
        /* ... */
    )

    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(150f, 100f) to smallView,
        SizeF(150f, 200f) to tallView,
        SizeF(215f, 100f) to wideView
    )
    val remoteViews = RemoteViews(viewMapping)

    appWidgetManager.updateAppWidget(id, remoteViews)
}

נניח שלפריט יש את המאפיינים הבאים:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

קטע הקוד שלמעלה אומר את הדברים הבאים:

  • smallView תומך ב-160dp‏ (minResizeWidth) × 110dp‏ (minResizeHeight) עד 160dp‏ × 199dp (נקודת החיתוך הבאה – 1dp).
  • tallView תומך ב-160dp × 200dp עד 214dp (נקודת החיתוך הבאה - 1) × 200dp.
  • ‫wideView תומך בגדלים מ-‎215dp × 110dp‏ (minResizeHeight) עד ‎250dp‏ (maxResizeWidth) × ‎200dp‏ (maxResizeHeight).

הווידג'ט צריך לתמוך בטווח הגדלים מ-minResizeWidth × minResizeHeight עד maxResizeWidth × maxResizeHeight. בטווח הזה, אתם יכולים להחליט מה תהיה נקודת החיתוך שבה הפריסה תשתנה.

דוגמה לפריסה רספונסיבית
איור 1. דוגמה לפריסה רספונסיבית.

לספק פריסות מדויקות

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

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

  1. עומס יתר AppWidgetProvider.onAppWidgetOptionsChanged(), שמופעל כשקבוצת הגדלים משתנה.

  2. קוראים לפונקציה AppWidgetManager.getAppWidgetOptions(), שמחזירה Bundle שמכיל את הגדלים.

  3. ניגשים למפתח AppWidgetManager.OPTION_APPWIDGET_SIZES מ-Bundle.

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

override fun onAppWidgetOptionsChanged(
    context: Context,
    appWidgetManager: AppWidgetManager,
    id: Int,
    newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, id, newOptions)
    // Get the new sizes.
    val sizes = newOptions?.let {
        BundleCompat.getParcelableArrayList(
            it,
            AppWidgetManager.OPTION_APPWIDGET_SIZES,
            SizeF::class.java
        )
    }
    // Check that the list of sizes is provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    // Map the sizes to the RemoteViews that you want.
    val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
    appWidgetManager.updateAppWidget(id, remoteViews)
}

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews {
}

בחירת גודל לווידג'ט

כל ווידג'ט חייב להגדיר targetCellWidth ו-targetCellHeight למכשירים עם Android מגרסה 12 ואילך, או minWidth ו-minHeight לכל הגרסאות של Android, כדי לציין את כמות השטח המינימלית שהוא תופס כברירת מחדל. עם זאת, כשמשתמשים מוסיפים ווידג'ט למסך הבית, בדרך כלל הוא תופס יותר מהרוחב והגובה המינימליים שציינתם.

במסכי הבית של Android יש רשת של מקומות פנויים שבהם המשתמשים יכולים להציב ווידג'טים וסמלים. הרשת הזו יכולה להשתנות בהתאם למכשיר. לדוגמה, בהרבה מכשירים ניידים יש רשת של 5x4, ובטאבלטים יכולה להיות רשת גדולה יותר. כשמוסיפים את הווידג'ט, הוא נמתח כדי לתפוס את מספר התאים המינימלי, אופקית ואנכית, שנדרש כדי לעמוד באילוצים של targetCellWidth ו-targetCellHeight במכשירים עם Android מגרסה 12 ואילך, או באילוצים של minWidth ו-minHeight במכשירים עם Android מגרסה 11 (רמת API‏ 30) ומטה.

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

מספר התאים (רוחב x גובה) הגודל הזמין במצב לאורך (dp) הגודל הזמין בפריסה לרוחב (dp)
1x1 57x102dp ‫127x51dp
‫2 על 1 130x102dp 269x51dp
3x1 203x102dp ‎412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp ‎697x51dp
5x2 349x220dp ‎697x117dp
5x3 349x337dp ‎697x184dp
5x4 349x455dp 697x250dp
... ... ...
n x m ‎(73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

השתמשו בגודל התאים של מצב לאורך כדי לקבוע את הערכים של המאפיינים minWidth,‏ minResizeWidth ו-maxResizeWidth. באופן דומה, השתמשו בגודלי התאים של פריסה לרוחב כדי לקבוע את הערכים שאתם מספקים למאפיינים minHeight,‏ minResizeHeight ו-maxResizeHeight.

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

לדוגמה, אם רוצים שיהיה אפשר לשנות את רוחב הווידג'ט לתא אחד ב-Google Pixel 4, צריך להגדיר את minResizeWidth ל-56dp לכל היותר כדי לוודא שהערך של המאפיין minResizeWidth קטן מ-57dp, כי הרוחב של תא הוא לפחות 57dp לאורך. באופן דומה, אם רוצים לשנות את הגובה של הווידג'ט בתא אחד באותו מכשיר, צריך להגדיר את minResizeHeight ל-50dp לכל היותר כדי לוודא שהערך של המאפיין minResizeHeight קטן מ-51dp, כי הגובה של תא אחד הוא לפחות 51dp בפריסה לרוחב.

אפשר לשנות את הגודל של כל ווידג'ט בטווח הגדלים שבין המאפיינים minResizeWidth/minResizeHeight ו-maxResizeWidth/maxResizeHeight, כלומר הוא צריך להתאים לכל טווח גדלים שביניהם.

לדוגמה, כדי להגדיר את גודל ברירת המחדל של הווידג'ט במיקום, אפשר להגדיר את המאפיינים הבאים:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

המשמעות היא שגודל ברירת המחדל של הווידג'ט הוא 3x2 תאים, כפי שצוין במאפיינים targetCellWidth ו-targetCellHeight, או 180×110dp, כפי שצוין ב-minWidth וב-minHeight למכשירים עם Android בגרסה 11 ומטה. במקרה השני, הגודל בתאים יכול להשתנות בהתאם למכשיר.

כדי להגדיר את טווחי הגודל הנתמכים של הווידג'ט, אפשר להגדיר את המאפיינים הבאים:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

כפי שצוין במאפיינים הקודמים, אפשר לשנות את רוחב הווידג'ט מ-180dp ל-530dp, ואת הגובה מ-110dp ל-450dp. אפשר לשנות את גודל הווידג'ט מ-3x2 ל-5x2 תאים, כל עוד מתקיימים התנאים הבאים:

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(180f, 110f) to smallView,
    SizeF(270f, 110f) to mediumView,
    SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

נניח שהווידג'ט משתמש בפריסות הרספונסיביות שמוגדרות בקטעי הקוד הקודמים. כלומר, הפריסה שצוינה בתור R.layout.widget_weather_forecast_small משמשת מ-180dp‏ (minResizeWidth) x‏ 110dp‏ (minResizeHeight) עד 269x279dp (נקודות החיתוך הבאות – 1). באופן דומה, R.layout.widget_weather_forecast_medium משמש מ-270x110dp עד 270x279dp, ו-R.layout.widget_weather_forecast_large משמש מ-270x280dp עד 530dp (maxResizeWidth) x 450dp (maxResizeHeight).

כשהמשתמש משנה את גודל הווידג'ט, המראה שלו משתנה בהתאם לכל גודל בתאים, כמו בדוגמאות הבאות.

דוגמה לווידג'ט מזג אוויר בגודל הקטן ביותר של רשת 3x2. בממשק המשתמש מוצגים שם המיקום (טוקיו), הטמפרטורה (14°) וסמל שמציין מזג אוויר מעונן חלקית.
איור 2. 3x2 R.layout.widget_weather_forecast_small.

דוגמה לווידג'ט מזג אוויר בגודל בינוני של 4x2. שינוי הגודל של הווידג'ט
            בצורה הזו מתבסס על כל ממשק המשתמש מהגודל הקודם של הווידג'ט,
            ומוסיף את התווית &quot;מעונן חלקית&quot; ותחזית של הטמפרטורות
            מ-16:00 עד 19:00.
איור 3. 4x2 R.layout.widget_weather_forecast_medium.

דוגמה לווידג'ט מזג אוויר בגודל בינוני של 5x2. שינוי גודל הווידג'ט
            בדרך הזו יוביל לאותו ממשק משתמש כמו בגודל הקודם, אבל הוא
            יימתח באורך של תא אחד כדי לתפוס יותר מקום אופקי.
איור 4. 5x2 R.layout.widget_weather_forecast_medium.

דוגמה לווידג'ט מזג אוויר בגודל &quot;גדול&quot; של 5x3. שינוי הגודל של הווידג'ט בדרך הזו מתבסס על כל ממשק המשתמש מהגדלים הקודמים של הווידג'ט, ומוסיף תצוגה בתוך הווידג'ט שמכילה תחזית של מזג האוויר ביום שלישי וביום רביעי. סמלים שמציינים מזג אוויר שמשי או גשום וטמפרטורות גבוהות ונמוכות לכל יום.
איור 5. 5x3 R.layout.widget_weather_forecast_large.

דוגמה לווידג'ט מזג אוויר בגודל &quot;גדול&quot; של 5x4. שינוי הגודל של הווידג'ט בצורה הזו מתבסס על כל ממשק המשתמש מהגדלים הקודמים של הווידג'ט, ומוסיף את חמישי ושישי (והסמלים המתאימים שלהם שמציינים את סוג מזג האוויר, וגם את הטמפרטורה הגבוהה והנמוכה בכל יום).
איור 6. 5x4 R.layout.widget_weather_forecast_large.