إنشاء تطبيق مصغّر باستخدام ميزة "نظرة سريعة"

توضّح الأقسام التالية كيفية إنشاء تطبيق مصغّر أساسي باستخدام 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 (الإصدار 12 من نظام التشغيل Android) وminWidth وminHeight
  • اعتبارًا من الإصدار 12 من نظام التشغيل Android، تحدّد السمتان targetCellWidth وtargetCellHeight الحجم التلقائي لأداة الشاشة من حيث خلايا الشبكة. يتم تجاهل هاتين السمتَين في الإصدار 11 من نظام التشغيل Android والإصدارات الأقدم، ويمكن تجاهلهما إذا كانت الشاشة الرئيسية لا تتيح استخدام تنسيق مستند إلى شبكة.
  • تحدّد السمتان 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 بكسل غير مرتبط بالكثافة وارتفاعها 50 بكسل غير مرتبط بالكثافة.
  • يتم توفير مواصفات السمات التالية:
<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 كالحجم التلقائي للأداة.

حجم التطبيق المصغّر هو 2×2 تلقائيًا. يمكن تغيير حجم التطبيق المصغّر إلى 2x1 أو إلى 4x3.

الإصدار 11 من نظام التشغيل Android والإصدارات الأقدم:

استخدِم السمتَين minWidth وminHeight لاحتساب الحجم التلقائي للأداة.

العرض التلقائي = Math.ceil(80 / 30) = 3

الارتفاع التلقائي = Math.ceil(80 / 50) = 2

يكون حجم التطبيق المصغّر 3×2 تلقائيًا. يمكن تغيير حجم التطبيق المصغّر إلى 2x1 أو إلى وضع ملء الشاشة.

سمات التطبيقات المصغّرة الإضافية

يوضّح الجدول التالي سمات <appwidget-provider> المتعلّقة بخصائص أخرى غير حجم التطبيق المصغّر.

السمات والوصف
updatePeriodMillis يحدّد هذا الإعداد عدد المرات التي يطلب فيها إطار عمل التطبيق المصغّر تحديثًا من GlanceAppWidgetReceiver من خلال استدعاء طريقة رد الاتصال onUpdate(). ننصحك بتعديل البيانات بأقل معدّل ممكن، أي مرة واحدة في الساعة كحد أقصى، للحفاظ على عمر البطارية. لمعرفة التفاصيل، يُرجى الاطّلاع على قسم حالات تحديث التطبيقات المصغّرة في إدارة حالة Glance.
initialLayout يشير إلى مورد التصميم الذي يحدّد تصميم تحميل التطبيق المصغّر قبل عرض تركيبات واجهة مستخدم Glance. يمكنك استخدام تنسيق التحميل المحدّد مسبقًا في المكتبة: @layout/glance_default_loading_layout.
configure تحدِّد نشاط الإعداد الذي يتم تشغيله عندما يضيف المستخدم التطبيق المصغّر. يُرجى الاطّلاع على دليل السماح للمستخدمين بضبط تطبيقات مصغّرة.
description يحدّد الوصف الذي ستعرضه أداة اختيار التطبيقات المصغّرة لتطبيقك المصغّر. تم طرح هذه الميزة في Android 12.
previewLayout (الإصدار 12 من نظام التشغيل Android) وpreviewImage (الإصدار 11 من نظام التشغيل Android والإصدارات الأقدم)
  • اعتبارًا من الإصدار 12 من نظام التشغيل Android، تحدّد السمة previewLayout معاينة قابلة للتوسيع، والتي تقدّمها كتنسيق XML تم ضبطه على الحجم التلقائي لأداة الشاشة. من الأفضل أن يشير هذا إلى تعيين XML ثابت يطابق تخطيط التصميم الخاص بك.
  • في الإصدار Android 11 أو الإصدارات الأقدم، تحدّد السمة previewImage لقطة شاشة ثابتة قابلة للرسم لشكل التطبيق المصغّر، وتظهر في أداة اختيار التطبيقات المصغّرة.
ننصحك بتحديد كليهما لكي يعود تطبيقك إلى الأنظمة الأساسية القديمة بسلاسة. بالنسبة إلى الأنظمة الأساسية الأحدث (الإصدار 15 من Android والإصدارات الأحدث)، يمكنك تحديد المعاينات التي يتم إنشاؤها مباشرةً في Kotlin باستخدام `GlanceAppWidget.providePreview`. راجِع دليل المعاينات التي يتم إنشاؤها.
autoAdvanceViewId يحدّد رقم تعريف العرض الفرعي للتطبيق المصغّر الذي يتم الانتقال التلقائي إليه من خلال مضيف التطبيق المصغّر.
widgetCategory تحدّد ما إذا كان يمكن عرض التطبيق المصغّر على الشاشة الرئيسية (home_screen) أو شاشة القفل (keyguard) أو كلتيهما. بالنسبة إلى الإصدار 5.0 من Android والإصدارات الأحدث، لا يكون سوى home_screen صالحًا.
widgetFeatures تُعلن عن الميزات التي يتيحها التطبيق المصغّر. على سبيل المثال، إذا كانت إعدادات التطبيق المصغّر اختيارية، حدِّد كلاً من configuration_optional وreconfigurable.

تحديد GlanceAppWidget

  1. أنشئ فئة جديدة تمتد من GlanceAppWidget وتلغي الطريقة 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 والإصدارات الأحدث) من خلال اتّباع الخطوات التالية:

  • فك ترميز الصور النقطية المشتركة قبل provideContent: إذا كان التطبيق المصغّر يستخدم SizeMode.Responsive أو SizeMode.Exact، يشغّل Glance دالة lambda 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. تعمل عمليات الاستدعاء الأساسية المتاحة في GlanceAppWidgetReceiver على النحو التالي:

  • ‫onUpdate(): يتم تجاوزه تلقائيًا من خلال Glance لتنفيذ تعديلات على التركيب. في حال إلغاء onUpdate يدويًا، يجب استدعاء super.onUpdate للسماح لـ Glance بتشغيل سلاسل المحادثات الخاصة بالإنشاء بنجاح.
  • ‫onAppWidgetOptionsChanged(): يتم استدعاؤها عند وضع التطبيق المصغّر لأول مرة أو تغيير حجمه. تقرأ Glance عناصر حزمة الخيارات في الخلفية لكي يتم تعديل التنسيق بسلاسة استنادًا إلى أبعاد وقت التشغيل.
  • 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: تحدّد نصف قطر زاوية حاوية خلفية التطبيق المصغّر (لا يزيد عن 28 وحدة بكسل مستقلة الكثافة).
  • نصف القطر الداخلي: لمنع اقتصاص المحتوى، احسب نصف قطر نسبي للمحتوى الداخلي استنادًا إلى المخطط التفصيلي لخلفية النظام: systemRadiusValue - widgetPadding

في Glance، يمكنك تطبيق سمات حجم نصف قطر الزاوية بشكل ديناميكي في التركيب باستخدام GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

لضمان التوافق مع الأنظمة القديمة على الأجهزة التي تعمل بنظام التشغيل Android 11 (مستوى واجهة برمجة التطبيقات 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>