Как создать виджет приложения с помощью Glance

В разделах ниже рассказывается, как создать базовый виджет приложения с помощью 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. Размер виджета можно изменить до 2 x 1 или 4 x 3.

Android 11 и более ранних версий

Атрибуты minWidth и minHeight используются для вычисления размера виджета по умолчанию.

Ширина по умолчанию = Math.ceil(80 / 30) = 3

Высота по умолчанию = Math.ceil(80 / 50) = 2

По умолчанию размер виджета – 3x2. Размер виджета можно уменьшить до 2 x 1 или увеличить до полноэкранного режима.

Дополнительные атрибуты виджетов

В таблице ниже описаны атрибуты <appwidget-provider>, относящиеся к другим характеристикам виджета, помимо его размера.

Атрибуты и описание
updatePeriodMillis Определяет, как часто фреймворк виджета запрашивает обновление у GlanceAppWidgetReceiver, вызывая метод обратного вызова onUpdate(). Чтобы сэкономить заряд батареи, рекомендуем обновлять данные как можно реже, но не чаще раза в час. Подробнее о том, когда обновлять виджеты…
initialLayout Указывает на ресурс макета, который определяет макет загрузки виджета до того, как будут отрисованы композиции Glance UI. Вы можете использовать стандартную разметку загрузки, которая есть в библиотеке: @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 и переопределяет метод 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.

Используйте класс 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 фильтрует и обрабатывает следующие основные широковещательные намерения виджетов платформы:

Создание интерфейса

Ниже приведен фрагмент кода, в котором показано, как создать интерфейс:

/* 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). Окончательный вид зависит от доступного пространства. На изображении ниже показано, как это может выглядеть.
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 (уровень 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>