יצירת ווידג'ט לאפליקציה באמצעות התכונה 'בקצרה'

בקטעים הבאים מוסבר איך ליצור ווידג'ט בסיסי לאפליקציה באמצעות Glance.

הצהרה על AppWidget במניפסט

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

  1. מרחיבים את המקלט AppWidget מ-GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. רושמים את הספק של הווידג'ט של האפליקציה בקובץ AndroidManifest.xml ובקובץ המטא-נתונים המשויך:

        <receiver android:name=".glance.MyReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/my_app_widget_info" />
    </receiver>
    

מוסיפים את המטא-נתונים של AppWidgetProviderInfo

אחר כך פועלים לפי המדריך יצירת ווידג'ט כדי ליצור ולהגדיר את פרטי הווידג'ט של האפליקציה בקובץ @xml/my_app_widget_info.

ההבדל היחיד ב-Glance הוא שאין XML של initialLayout, אבל צריך להגדיר אחד. אתם יכולים להשתמש בפריסת הטעינה המוגדרת מראש שזמינה בספרייה:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

הצהרה על קובץ ה-XML של AppWidgetProviderInfo

האובייקט AppWidgetProviderInfo מגדיר את המאפיינים החיוניים של הווידג'ט. מגדירים את AppWidgetProviderInfo בקובץ המשאבים של המטא-נתונים ב-XML‏ (res/xml/my_app_widget_info.xml) בתוך רכיב <appwidget-provider>:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

מאפייני גודל הווידג'ט

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

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

בטבלה הבאה מתוארים המאפיינים של <appwidget-provider> שקשורים לגודל הווידג'ט:

מאפיינים ותיאור
‫targetCellWidth ו-targetCellHeight (Android 12),‏ minWidth ו-minHeight
  • החל מ-Android 12, המאפיינים targetCellWidth ו-targetCellHeight מציינים את גודל ברירת המחדל של הווידג'ט במונחים של תאי רשת. המערכת מתעלמת מהמאפיינים האלה ב-Android מגרסה 11 ומטה, ויכולה להתעלם מהם אם מסך הבית לא תומך בפריסה שמבוססת על רשת.
  • המאפיינים minWidth ו-minHeight מציינים את גודל ברירת המחדל של הווידג'ט ב-dp. אם הערכים של רוחב או גובה מינימליים של ווידג'ט לא תואמים לממדים של התאים, הערכים מעוגלים כלפי מעלה לגודל התא הקרוב ביותר.
מומלץ לציין את שתי קבוצות המאפיינים – targetCellWidth ו-targetCellHeight, ו-minWidth ו-minHeight – כדי שהאפליקציה תוכל להשתמש ב-minWidth וב-minHeight כגיבוי אם המכשיר של המשתמש לא תומך ב-targetCellWidth וב-targetCellHeight. אם יש תמיכה, המאפיינים targetCellWidth ו-targetCellHeight מקבלים קדימות על פני המאפיינים minWidth ו-minHeight.
‫minResizeWidth ו-minResizeHeight מציינים את הגודל המינימלי המוחלט של הווידג'ט. הערכים האלה מציינים את הגודל שמתחתיו הווידג'ט לא קריא או לא שמיש. המאפיינים האלה מאפשרים למשתמשים לשנות את גודל הווידג'ט לגודל קטן יותר מגודל ברירת המחדל של הווידג'ט. המערכת מתעלמת מהמאפיין minResizeWidth אם הוא גדול מ-minWidth או אם שינוי הגודל האופקי לא מופעל. למידע נוסף, ראו resizeMode. באופן דומה, המערכת מתעלמת מהמאפיין minResizeHeight אם הוא גדול מ-minHeight או אם שינוי הגודל האנכי לא מופעל.
‫maxResizeWidth ו-maxResizeHeight מציינים את הגודל המקסימלי המומלץ של הווידג'ט. אם הערכים הם לא כפולה של מידות התא ברשת, הם מעוגלים כלפי מעלה לגודל התא הקרוב ביותר. המערכת מתעלמת מהמאפיין maxResizeWidth אם הוא קטן מ-minWidth או אם שינוי הגודל האופקי לא מופעל. resizeMode. באופן דומה, המערכת מתעלמת מהמאפיין maxResizeHeight אם הוא קטן מ-minHeight או אם שינוי הגודל האנכי לא מופעל. התכונה נוספה ב-Android 12.
resizeMode מציין את הכללים שלפיהם אפשר לשנות את הגודל של ווידג'ט. אתם יכולים להשתמש במאפיין הזה כדי לשנות את הגודל של הווידג'טים במסך הבית בצורה אופקית, אנכית או בשני הצירים. המשתמשים לוחצים לחיצה ארוכה על הווידג'ט כדי להציג את נקודות האחיזה לשינוי הגודל, ואז גוררים את נקודות האחיזה האופקיות או האנכיות כדי לשנות את הגודל שלו בפריסת הרשת. הערכים של המאפיין resizeMode כוללים את horizontal,‏ vertical ו-none. כדי להגדיר שאפשר לשנות את הגודל של ווידג'ט גם לרוחב וגם לאורך, משתמשים ב-horizontal|vertical.

דוגמה

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

  • הרוחב של תא ברשת הוא ‎30 dp והגובה הוא ‎50 dp.
  • מפרט המאפיין הבא:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

ב-Android מגרסה 12 ואילך:

משתמשים במאפיינים targetCellWidth ו-targetCellHeight כגודל ברירת המחדל של הווידג'ט.

כברירת מחדל, גודל הווידג'ט הוא 2x2. אפשר לשנות את גודל הווידג'ט ל-2x1 או ל-4x3.

Android מגרסה 11 ומטה:

משתמשים במאפיינים minWidth ו-minHeight כדי לחשב את גודל ברירת המחדל של הווידג'ט.

רוחב ברירת המחדל = Math.ceil(80 / 30) = 3

גובה ברירת המחדל = Math.ceil(80 / 50) = 2

כברירת מחדל, גודל הווידג'ט הוא 3x2. אפשר לשנות את גודל הווידג'ט ל-2x1 או למסך מלא.

מאפיינים נוספים של הווידג'ט

בטבלה הבאה מתוארים המאפיינים של <appwidget-provider> שקשורים לתכונות אחרות, מלבד גודל הווידג'ט.

מאפיינים ותיאור
updatePeriodMillis מגדירה את התדירות שבה מסגרת הווידג'ט מבקשת עדכון מ-GlanceAppWidgetReceiver באמצעות קריאה לשיטת הקריאה החוזרת (callback) onUpdate(). כדי לחסוך בסוללה, מומלץ לעדכן כמה שפחות, ולא יותר מפעם בשעה. לפרטים נוספים, קראו את הקטע מתי לעדכן ווידג'טים במאמר "ניהול המצבים של Glance".
initialLayout מפנה למשאב הפריסה שמגדיר את פריסת הטעינה של הווידג'ט לפני שמתבצע רינדור של הקומפוזיציות של ממשק המשתמש של Glance. אפשר להשתמש בפריסת הטעינה המוגדרת מראש שזמינה בספרייה: @layout/glance_default_loading_layout.
configure מגדיר את פעילות ההגדרה שמופעלת כשהמשתמש מוסיף את הווידג'ט. איך מאפשרים למשתמשים להגדיר ווידג'טים של אפליקציות
description מציין את התיאור שיוצג לווידג'ט בכלי לבחירת ווידג'טים. התכונה נוספה ב-Android 12.
‫previewLayout (Android מגרסה 12) ו-previewImage (Android מגרסה 11 ומטה)
  • החל מ-Android 12, המאפיין previewLayout מציין תצוגה מקדימה שניתן לשנות את הגודל שלה, שאתם מספקים כפריסת XML שמוגדרת לגודל ברירת המחדל של הווידג'ט. מומלץ שהמדיניות הזו תצביע על מיפוי XML סטטי שתואם לפריסת העיצוב שלך.
  • ב-Android מגרסה 11 ומטה, המאפיין previewImage מציין צילום מסך של תמונה סטטית שניתן לשרטט, שמראה איך הווידג'ט נראה ומופיע בכלי לבחירת ווידג'טים.
מומלץ לציין את שניהם כדי שהאפליקציה תפעל בצורה חלקה בפלטפורמות ישנות יותר. בפלטפורמות חדשות יותר (Android 15 ואילך), אפשר להגדיר תצוגות מקדימות שנוצרות בזמן אמת ב-Kotlin באמצעות `GlanceAppWidget.providePreview`. מידע נוסף זמין במדריך ליצירת תצוגות מקדימות.
autoAdvanceViewId מציין את מזהה התצוגה של תצוגת המשנה של הווידג'ט, שמועברת אוטומטית על ידי המארח של הווידג'ט.
widgetCategory מצהירה אם הווידג'ט יכול להופיע במסך הבית (home_screen), במסך הנעילה (keyguard) או בשניהם. ב-Android מגרסה 5.0 ואילך, רק home_screen תקף.
widgetFeatures מצהיר על התכונות שהווידג'ט תומך בהן. לדוגמה, אם הגדרת הווידג'ט היא אופציונלית, צריך לציין גם את configuration_optional וגם את reconfigurable.

הגדרה של GlanceAppWidget

  1. יוצרים מחלקה חדשה שמרחיבה את GlanceAppWidget ומבטלת את ה-method‏ provideGlance. זו השיטה שבה אפשר לטעון נתונים שנדרשים כדי לעבד את הווידג'ט:

    class MyAppWidget : GlanceAppWidget() {
    
        override suspend fun provideGlance(context: Context, id: GlanceId) {
    
            // In this method, load data needed to render the AppWidget.
            // Use `withContext` to switch to another thread for long running
            // operations.
    
            provideContent {
                // create your AppWidget here
                Text("Hello World")
            }
        }
    }

  2. יוצרים מופע שלו ב-glanceAppWidget ב-GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

עכשיו הגדרתם AppWidget באמצעות Glance.

ניהול הזיכרון של תמונות בווידג'טים לפי קטגוריות גודל

כשבווידג'ט מוצגים קובצי ביטמאפ או מזהי URI של תמונות ב-content://, צריך לשמור על הזיכרון הכולל של התמונות ב-RemoteViews שעברו סריאליזציה במסגרת מגבלת המערכת (6 * width * height בייטים, נאכפת באמצעות IllegalArgumentException ב-Android בגרסה 17 ואילך) על ידי ביצוע הפעולות הבאות:

  • פענוח של מפות סיביות (bitmap) משותפות לפני provideContent: אם הווידג'ט משתמש ב-SizeMode.Responsive או ב-SizeMode.Exact, Glance מפעיל את פונקציית הלמבדה provideContent פעם אחת לכל קטגוריה של DpSize. פענוח של Bitmap בתוך provideContent מקצה מופע נפרד של Bitmap לכל קטגוריה, ומכפיל את השימוש בזיכרון ב-RemoteViews.mBitmapCache. מפענחים את מפות הסיביות שעברו דגימה מופחתת (Bitmap.Config.RGB_565 לתמונות אטומות) פעם אחת בתוך provideGlance לפני provideContent, כדי שכל הקטגוריות ישתפו הפניה אחת של Bitmap.
  • משתמשים בייבוא התואם של ImageProvider: Jetpack Glance מספק שתי פונקציות נפרדות של ImageProvider בשתי חבילות:

    • משתמשים ב-import androidx.glance.ImageProvider כשמעבירים ImageProvider(bitmap: Bitmap) בזיכרון או פריט גרפי שניתן להזזה ImageProvider(resId: Int).
    • משתמשים ב-import androidx.glance.appwidget.ImageProvider כשמעבירים URI ImageProvider(uri: Uri). כשמציגים קבצים שנשמרו במטמון דרך URI של FileProvider, צריך להעניק את ההרשאה Intent.FLAG_GRANT_READ_URI_PERMISSION לכל חבילת מרכז אפליקציות של Intent.CATEGORY_HOME שמותקנת, כדי שהמארח יוכל לעבד את התמונה.

שימוש בכיתה GlanceAppWidgetReceiver כדי לטפל בשידורי ווידג'טים

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

הצהרה על ווידג'ט במניפסט

מגדירים את מחלקת המשנה של המחלקה GlanceAppWidgetReceiver כמקלט שידור בקובץ AndroidManifest.xml:

<receiver android:name="MyReceiver"
          android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/my_app_widget_info" />
</receiver>

הרכיב <receiver> דורש את המאפיין android:name, שמציין את מחלקת המקלט. המקלט חייב לקבל את פעולת השידור ACTION_APPWIDGET_UPDATE בתוך <intent-filter>.

הרכיב <meta-data> צריך לזהות את השם שלו בתור android.appwidget.provider, והמאפיין android:resource צריך להצביע על משאב המטא-נתונים של AppWidgetProviderInfo בפורמט XML‏ (@xml/my_app_widget_info).

הטמעת המחלקה GlanceAppWidgetReceiver

ב-Glance, מרחיבים את GlanceAppWidgetReceiver במקום את AppWidgetProvider ישירות. כדי להטמיע את זה, צריך לקשר את המקלט למכונה של GlanceAppWidget. הקריאות החוזרות (callbacks) העיקריות שזמינות ב-GlanceAppWidgetReceiver פועלות באופן הבא:

  • ‫onUpdate(): מוחלף באופן אוטומטי על ידי Glance כדי לבצע עדכונים בקומפוזיציה. אם אתם משנים באופן ידני את onUpdate, אתם צריכים להפעיל את super.onUpdate כדי לאפשר ל-Glance להפעיל בהצלחה את ה-threads של הקומפוזיציה.
  • ‫onAppWidgetOptionsChanged(): נקראת כשהווידג'ט ממוקם בפעם הראשונה או כשמשנים את הגודל שלו. האפשרויות של Glance reads מקבצות פריטים מאחורי הקלעים, כך שהפריסה מתאימה את עצמה בצורה חלקה על סמך המאפיינים של זמן הריצה.
  • ‫onDeleted(Context, IntArray): מופעל בכל פעם שהמשתמש מוחק מופע ספציפי של ווידג'ט.
  • ‫onEnabled(Context): מופעל כשהמופע הראשון של הווידג'ט נוצר בהצלחה. מעולה להעברות גלובליות.
  • ‫onDisabled(Context): מופעלת כשמוסר המופע הפעיל האחרון של הספק.
  • ‫onReceive(Context, Intent): מיירט כל שידור של הפלטפורמה לפני שיטות קריאה חוזרת ספציפיות. חשוב לוודא שכל לוגיקה של מקלט בהתאמה אישית שאתם כותבים קוראת ל-super.onReceive(context, intent), ולעולם לא קוראת ל-goAsync בעצמה, כי Glance מנתב את העבודה באופן אסינכרוני.

קבלת כוונות שידור של ווידג'טים

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

יצירת ממשק משתמש

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

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize(),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

דוגמת הקוד שלמעלה מבצעת את הפעולות הבאות:

  • ברמה העליונה Column, הפריטים ממוקמים אנכית אחד אחרי השני.
  • ה-Column מתרחב כדי להתאים למקום הפנוי (באמצעות GlanceModifier), והתוכן שלו מיושר לחלק העליון (verticalAlignment) וממורכז אופקית (horizontalAlignment).
  • התוכן של Column מוגדר באמצעות lambda. הסדר חשוב.
    • הפריט הראשון ב-Column הוא רכיב Text עם ריפוד של 12.dp.
    • הפריט השני הוא Row, שבו הפריטים ממוקמים אופקית אחד אחרי השני, עם שני Buttons ממורכזים אופקית (horizontalAlignment). התצוגה הסופית תלויה במקום הפנוי. התמונה הבאה היא דוגמה לאיך שההודעה עשויה להיראות:
destination_widget
תרשים 1. דוגמה לממשק משתמש.

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

הטמעת פינות מעוגלות

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

  • ‫system_app_widget_background_radius: מציין את רדיוס הפינה של מאגר הרקע של הווידג'ט (אף פעם לא גדול מ-28dp).
  • רדיוס פנימי: כדי למנוע חיתוך של התוכן, מחשבים רדיוס פרופורציונלי לתוכן הפנימי על סמך קווי המתאר של הרקע של המערכת: systemRadiusValue - widgetPadding

ב-Glance, אפשר להחיל מאפייני גודל של רדיוס פינה באופן דינמי בקומפוזיציה באמצעות GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

כדי לשמור על תאימות לדורות קודמים במכשירים עם Android 11 (רמת API‏ 30) ומטה, צריך להטמיע מאפיינים מותאמים אישית וחלופות למשאבים של עיצובים מותאמים אישית:

  • /values/attrs.xml

    <resources>
    <attr name="backgroundRadius" format="dimension" />
    </resources>
    
  • /values/styles.xml

    <resources>
    <style name="MyWidgetTheme">
      <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
    </style>
    </resources>
    
  • /values-31/styles.xml

    <resources>
    <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
      <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
    </style>
    </resources>
    
  • /drawable/my_widget_background.xml

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="?attr/backgroundRadius" />
    </shape>