В следующих разделах описывается, как создать базовый виджет приложения с помощью Glance.
Объявите AppWidget в манифесте
После выполнения шагов по настройке объявите AppWidget и его метаданные в приложении.
Расширьте приемник
AppWidgetизGlanceAppWidgetReceiver:class MyAppWidgetReceiver : GlanceAppWidgetReceiver() { override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget") }
Зарегистрируйте поставщика виджета приложения в файле
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 |
targetCellWidth и targetCellHeight, а также minWidth и minHeight. В этом случае, если устройство пользователя не поддерживает targetCellWidth и targetCellHeight, приложение сможет использовать minWidth и minHeight. Если поддерживаются атрибуты 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, чтобы задать размер виджета по умолчанию.
По умолчанию размер виджета – 2 x 2. Размер виджета можно уменьшить до 2 x 1 или увеличить до 4 x 3.
Android 11 и более ранних версий
Используйте атрибуты minWidth и minHeight, чтобы вычислить размер виджета по умолчанию.
Ширина по умолчанию = Math.ceil(80 / 30) = 3
Высота по умолчанию = Math.ceil(80 / 50) = 2.
По умолчанию размер виджета составляет 3 x 2. Размер виджета можно уменьшить до 2 x 1 или увеличить до полного экрана.
Дополнительные атрибуты виджета
В таблице ниже описаны атрибуты <appwidget-provider>, не связанные с размером виджета.
| Атрибуты и описание | |
|---|---|
updatePeriodMillis |
Определяет, как часто фреймворк виджета запрашивает обновление у GlanceAppWidgetReceiver, вызывая метод обратного вызова onUpdate(). Чтобы экономить заряд батареи, рекомендуем обновлять данные как можно реже, например не чаще одного раза в час.
Подробную информацию можно найти в разделе Когда обновлять виджеты в статье об управлении состояниями Glance. |
initialLayout |
Указывает на ресурс разметки, который определяет макет загрузки виджета до отрисовки композиций интерфейса Glance. Вы можете использовать стандартный макет загрузки, доступный в библиотеке: @layout/glance_default_loading_layout. |
configure |
Определяет действие конфигурации, которое запускается, когда пользователь добавляет виджет. Ознакомьтесь с руководством Как разрешить пользователям настраивать виджеты приложений. |
description |
Задает описание, которое будет показываться в окне выбора виджетов. Появился в Android 12. |
previewLayout (Android 12) и previewImage (Android 11 и более ранние версии). |
|
autoAdvanceViewId |
Указывает идентификатор представления подпредставления виджета, которое автоматически продвигается хостом виджета. |
widgetCategory |
Указывает, где может показываться виджет: на главном экране (home_screen), заблокированном экране (keyguard) или на обоих. Для Android 5.0 и более поздних версий допустимо только значение home_screen. |
widgetFeatures |
Объявляет функции, поддерживаемые виджетом. Например, если настройка виджета необязательна, укажите и configuration_optional, и reconfigurable. |
GlanceAppWidget: определение
Создайте новый класс, который расширяет
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") } } }
Создайте экземпляр в методе
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 запускает лямбда-выражение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). - При передаче URI используйте
import androidx.glance.appwidget.ImageProvider.ImageProvider(uri: Uri). При показе кешированных файлов через URIFileProviderпредоставьте разрешение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 должен указывать на ресурс метаданных XML AppWidgetProviderInfo (@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 фильтрует и обрабатывает следующие базовые широковещательные намерения виджетов платформы:
ACTION_APPWIDGET_UPDATEACTION_APPWIDGET_DELETEDACTION_APPWIDGET_ENABLEDACTION_APPWIDGET_DISABLEDACTION_APPWIDGET_OPTIONS_CHANGED
Создать интерфейс
В следующем фрагменте кода показано, как создать интерфейс:
/* 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определяется с помощью лямбда-выражения. Порядок важен.- Первый элемент в
Column– это компонентTextс отступом12.dp. - Второй элемент –
Row, в котором элементы размещаются горизонтально один за другим, с двумяButtons, выровненными по центру по горизонтали (horizontalAlignment). Окончательный вид зависит от доступного пространства. Вот как это может выглядеть:
- Первый элемент в
Вы можете изменить значения выравнивания или применить другие значения модификаторов (например, отступы), чтобы изменить размещение и размер компонентов. Полный список компонентов, параметров и модификаторов для каждого класса приведен в справочной документации.
Как реализовать скругленные углы
В Android 12 появились системные параметры, позволяющие динамически настраивать радиус закругления углов виджетов:
system_app_widget_background_radius– радиус закругления углов контейнера виджета (не более 28 dp).- Внутренний радиус. Чтобы контент не обрезался, рассчитайте пропорциональный радиус для внутреннего контента на основе контура системного фона:
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>