Создайте простой виджет

Попробуйте способ создания композиций.
Jetpack Compose — рекомендуемый набор инструментов для создания пользовательского интерфейса для Android. Узнайте, как создавать виджеты, используя API в стиле Compose.

Виджеты приложений — это миниатюрные представления приложений, которые можно встраивать в другие приложения, например, на главный экран, и получать периодические обновления. В пользовательском интерфейсе эти представления называются виджетами , и вы можете опубликовать один из них с помощью поставщика виджетов приложения (или поставщика виджетов ). Компонент приложения, содержащий другие виджеты, называется хостом виджетов приложения (или хостом виджетов ). На рисунке 1 показан пример музыкального виджета:

Пример музыкального виджета
Рисунок 1. Пример музыкального виджета.

В этом документе описывается, как опубликовать виджет с помощью поставщика виджетов. Подробную информацию о создании собственного AppWidgetHost для размещения виджетов приложения см. в разделе «Создание хоста виджетов» .

Информацию о том, как создать виджет, см. в разделе «Обзор виджетов приложений» .

Компоненты виджетов

Для создания виджета вам потребуются следующие основные компоненты:

Объект AppWidgetProviderInfo
Описывает метаданные виджета, такие как макет виджета, частота обновления и класс AppWidgetProvider . AppWidgetProviderInfo определяется в XML , как описано в этом документе.
класс AppWidgetProvider
Определяет основные методы, позволяющие программно взаимодействовать с виджетом. Через него вы получаете широковещательные сообщения об обновлении, включении, отключении или удалении виджета. Вы объявляете AppWidgetProvider в манифесте , а затем реализуете его, как описано в этом документе.
Просмотреть макет
Определяет начальную компоновку виджета. Компоновка задается в формате XML , как описано в этом документе.

На рисунке 2 показано, как эти компоненты вписываются в общий процесс обработки виджетов приложения.

Процесс обработки виджетов приложения
Рисунок 2. Схема обработки виджета приложения.

Если вашему виджету требуется настройка пользователем, реализуйте действие настройки виджета приложения. Это действие позволяет пользователям изменять параметры виджета — например, часовой пояс для виджета часов.

Мы также рекомендуем следующие улучшения: гибкая компоновка виджетов , различные усовершенствования , расширенные виджеты , виджеты-коллекции и создание хоста для виджетов .

Объявите XML-файл AppWidgetProviderInfo.

Объект AppWidgetProviderInfo определяет основные характеристики виджета. Определите объект AppWidgetProviderInfo в XML-файле ресурсов, используя один элемент <appwidget-provider> , и сохраните его в папке res/xml/ проекта.

Это показано в следующем примере:

<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/example_loading_appwidget"
    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 знаков после запятой в ширину и 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. Размер виджета можно уменьшить до 2х1 или увеличить до 4х3.

Android 11 и ниже:

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

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

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

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

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

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

Атрибуты и описание
updatePeriodMillis Определяет, как часто платформа виджетов запрашивает обновление у AppWidgetProvider , вызывая метод обратного вызова onUpdate() . Фактическое обновление не гарантируется точно в срок при этом значении, и мы рекомендуем обновлять как можно реже — не чаще одного раза в час — для экономии заряда батареи. Полный список рекомендаций по выбору подходящего периода обновления см. в разделе «Оптимизация обновления содержимого виджета» .
initialLayout Указывает на ресурс макета, определяющий расположение виджета.
configure Определяет активность, которая запускается при добавлении виджета пользователем, позволяя ему настраивать свойства виджета. См. раздел «Предоставление пользователям возможности настраивать виджеты» . Начиная с Android 12, ваше приложение может пропустить начальную настройку. См. раздел «Использование конфигурации виджета по умолчанию» для получения подробной информации.
description Задает описание для средства выбора виджета, которое будет отображаться для вашего виджета. Введено в Android 12.
previewLayout (Android 12) и previewImage (Android 11 и ниже)
  • Начиная с Android 12, атрибут previewLayout задает масштабируемый предварительный просмотр, который предоставляется в виде XML-макета, заданного размером виджета по умолчанию. В идеале, XML-макет, указанный в этом атрибуте, должен совпадать с XML-макетом фактического виджета с реалистичными значениями по умолчанию.
  • В Android 11 и более ранних версиях атрибут previewImage задает предварительный просмотр того, как будет выглядеть виджет после его настройки, который пользователь видит при выборе виджета приложения. Если атрибут не указан, пользователь вместо этого видит значок запуска вашего приложения. Это поле соответствует атрибуту android:previewImage в элементе <receiver> в файле AndroidManifest.xml .
Примечание: Мы рекомендуем указывать атрибуты previewImage и previewLayout , чтобы ваше приложение могло использовать previewImage если устройство пользователя не поддерживает previewLayout . Для получения более подробной информации см. раздел «Обратная совместимость с масштабируемыми предварительными просмотрами виджетов» .
autoAdvanceViewId Указывает идентификатор представления дочернего элемента виджета, который автоматически перемещается в зависимости от хоста виджета.
widgetCategory Определяет, может ли ваш виджет отображаться на главном экране ( home_screen ), экране блокировки ( keyguard ) или на обоих. Для Android 5.0 и выше допустимо только значение home_screen .
widgetFeatures Объявляет поддерживаемые виджетом функции. Например, если вы хотите, чтобы ваш виджет использовал свою конфигурацию по умолчанию при добавлении пользователем, укажите флаги configuration_optional и reconfigurable . Это позволит избежать запуска процесса настройки после добавления виджета пользователем. Пользователь все равно сможет перенастроить виджет позже.

Для обработки широковещательных сообщений виджетов используйте класс AppWidgetProvider.

Класс AppWidgetProvider обрабатывает широковещательные сообщения виджета и обновляет виджет в ответ на события жизненного цикла виджета. В следующих разделах описывается, как объявить AppWidgetProvider в манифесте, а затем реализовать его.

Объявите виджет в манифесте.

Сначала объявите класс AppWidgetProvider в файле AndroidManifest.xml вашего приложения, как показано в следующем примере:

<receiver android:name="ExampleAppWidgetProvider"
                 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/example_appwidget_info" />
</receiver>

Элемент <receiver> требует атрибута android:name , который указывает AppWidgetProvider , используемый виджетом. Компонент не должен экспортироваться, если только отдельному процессу не требуется передавать широковещательные сообщения вашему AppWidgetProvider , что обычно не требуется.

Элемент <intent-filter> должен включать элемент <action> с атрибутом android:name . Этот атрибут указывает, что AppWidgetProvider принимает широковещательное сообщение ACTION_APPWIDGET_UPDATE . Это единственное широковещательное сообщение, которое необходимо явно объявить. AppWidgetManager автоматически отправляет все остальные широковещательные сообщения виджета в AppWidgetProvider по мере необходимости.

Элемент <meta-data> указывает ресурс AppWidgetProviderInfo и требует наличия следующих атрибутов:

  • android:name : указывает имя метаданных. Используйте android.appwidget.provider для идентификации данных в качестве дескриптора AppWidgetProviderInfo .
  • android:resource : указывает местоположение ресурса AppWidgetProviderInfo .

Реализуйте класс AppWidgetProvider.

Класс AppWidgetProvider наследует BroadcastReceiver как вспомогательный класс для обработки широковещательных сообщений от виджетов. Он получает только те широковещательные сообщения, которые имеют отношение к виджету, например, когда виджет обновляется, удаляется, включается и отключается. При возникновении этих широковещательных событий вызываются следующие методы AppWidgetProvider :

onUpdate()
Эта функция вызывается для обновления виджета с интервалами, определенными атрибутом updatePeriodMillis в AppWidgetProviderInfo . Дополнительную информацию см. в таблице с описанием дополнительных атрибутов виджета на этой странице.
Этот метод также вызывается, когда пользователь добавляет виджет, поэтому он выполняет необходимую настройку, такую ​​как определение обработчиков событий для объектов View или запуск заданий для загрузки данных, отображаемых в виджете. Однако, если вы объявляете действие конфигурации без флага configuration_optional , этот метод не вызывается при добавлении виджета пользователем, но вызывается при последующих обновлениях. Ответственность за выполнение первого обновления после завершения конфигурации лежит на действии конфигурации. Дополнительную информацию см. в разделе « Предоставление пользователям возможности настраивать виджеты приложений» .
Наиболее важная функция обратного вызова — это onUpdate() . Дополнительную информацию см. в разделе «Обработка событий с помощью класса onUpdate() на этой странице.
onAppWidgetOptionsChanged()

Эта функция вызывается при первом размещении виджета и всякий раз, когда виджет изменяется в размере. Используйте этот коллбэк для отображения или скрытия контента в зависимости от диапазона размеров виджета. Получите диапазоны размеров — и, начиная с Android 12, список возможных размеров, которые может принимать экземпляр виджета — вызвав метод getAppWidgetOptions() , который возвращает Bundle , включающий следующее:

  • OPTION_APPWIDGET_MIN_WIDTH : содержит нижнюю границу ширины экземпляра виджета в единицах dp.
  • OPTION_APPWIDGET_MIN_HEIGHT : содержит нижнюю границу высоты экземпляра виджета в единицах dp.
  • OPTION_APPWIDGET_MAX_WIDTH : содержит верхнюю границу ширины экземпляра виджета в единицах dp.
  • OPTION_APPWIDGET_MAX_HEIGHT : содержит верхнюю границу высоты экземпляра виджета в единицах dp.
  • OPTION_APPWIDGET_SIZES : содержит список возможных размеров ( List<SizeF> ) в единицах dp, которые может принимать экземпляр виджета. Введено в Android 12.
onDeleted(Context, int[])

Эта команда вызывается каждый раз, когда виджет удаляется из хоста виджетов.

onEnabled(Context)

Эта функция вызывается при первом создании экземпляра виджета. Например, если пользователь добавляет два экземпляра вашего виджета, она вызывается только при первом создании. Если вам нужно открыть новую базу данных или выполнить другую настройку, которая должна произойти только один раз для всех экземпляров виджета, то это подходящее место для этого.

onDisabled(Context)

Эта функция вызывается, когда последний экземпент вашего виджета удаляется из хоста виджета. Здесь вы выполняете очистку всех операций, выполненных в onEnabled(Context) , например, удаление временной базы данных.

onReceive(Context, Intent)

Этот метод вызывается для каждого широковещательного сообщения и перед каждым из предшествующих методов обратного вызова. Обычно вам не нужно реализовывать этот метод, поскольку реализация AppWidgetProvider по умолчанию фильтрует все широковещательные сообщения виджетов и вызывает предшествующие методы по мере необходимости.

Необходимо объявить реализацию класса AppWidgetProvider как широковещательный приемник, используя элемент <receiver> в AndroidManifest . Дополнительную информацию см. в разделе «Объявление виджета в манифесте» на этой странице.

Обрабатывайте события с помощью класса onUpdate().

Наиболее важным коллбэком AppWidgetProvider является onUpdate() , поскольку он вызывается при добавлении каждого виджета к хосту, если только вы не используете действие конфигурации без флага configuration_optional . Если ваш виджет принимает какие-либо события взаимодействия с пользователем, зарегистрируйте обработчики событий в этом коллбэке. Если ваш виджет не создает временные файлы или базы данных, или не выполняет другую работу, требующую очистки, то onUpdate() может быть единственным методом коллбэка, который вам нужно определить.

Например, если вам нужен виджет с кнопкой, которая при нажатии запускает активность, вы можете использовать следующую реализацию AppWidgetProvider :

Котлин

class ExampleAppWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        appWidgetIds.forEach { appWidgetId ->
            // Create an Intent to launch ExampleActivity.
            val pendingIntent: PendingIntent = PendingIntent.getActivity(
                    /* context = */ context,
                    /* requestCode = */  0,
                    /* intent = */ Intent(context, ExampleActivity::class.java),
                    /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            val views: RemoteViews = RemoteViews(
                    context.packageName,
                    R.layout.appwidget_provider_layout
            ).apply {
                setOnClickPendingIntent(R.id.button, pendingIntent)
            }

            // Tell the AppWidgetManager to perform an update on the current
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

Java

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Perform this loop procedure for each widget that belongs to this
        // provider.
        for (int i=0; i < appWidgetIds.length; i++) {
            int appWidgetId = appWidgetIds[i];
            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(context, ExampleActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(
                /* context = */ context,
                /* requestCode = */ 0,
                /* intent = */ intent,
                /* flags = */ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
            );

            // Get the layout for the widget and attach an onClick listener to
            // the button.
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget_layout);
            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // Tell the AppWidgetManager to perform an update on the current app
            // widget.
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

Этот AppWidgetProvider определяет только метод onUpdate() , используя его для создания PendingIntent , который запускает Activity и прикрепляет его к кнопке виджета с помощью setOnClickPendingIntent(int, PendingIntent) . Он включает цикл, который перебирает каждую запись в appWidgetIds , массиве идентификаторов, определяющих каждый виджет, созданный этим провайдером. Если пользователь создает более одного экземпляра виджета, то все они обновляются одновременно. Однако для всех экземпляров виджета управляется только одно расписание updatePeriodMillis . Например, если расписание обновления определено как каждые два часа, и второй экземпляр виджета добавляется через час после первого, то оба обновляются с периодом, определенным первым, а второй период обновления игнорируется. Оба обновляются каждые два часа, а не каждый час.

Для получения более подробной информации см. пример класса ExampleAppWidgetProvider.java .

Получать широковещательные намерения виджета

AppWidgetProvider — это вспомогательный класс. Если вы хотите получать широковещательные сообщения от виджетов напрямую, вы можете реализовать собственный BroadcastReceiver или переопределить коллбэк onReceive(Context,Intent) . Вам нужно учитывать следующие интенты:

Создайте макет виджета

Необходимо задать начальный макет для вашего виджета в формате XML и сохранить его в каталоге res/layout/ проекта. Подробности см. в разделе «Рекомендации по дизайну» .

Создание макета виджета не представляет сложности, если вы знакомы с макетами . Однако имейте в виду, что макеты виджетов основаны на RemoteViews , который не поддерживает все типы макетов или виджетов представления. Вы не можете использовать пользовательские представления или подклассы представлений, поддерживаемых RemoteViews .

RemoteViews также поддерживает ViewStub , который представляет собой невидимый View нулевого размера, который можно использовать для отложенного создания ресурсов макета во время выполнения.

Поддержка поведения, сохраняющего состояние

В Android 12 добавлена ​​поддержка управления состоянием с использованием следующих существующих компонентов:

Виджет по-прежнему не имеет состояния. Ваше приложение должно хранить состояние и регистрироваться для событий изменения состояния.

Пример виджета списка покупок, демонстрирующего поведение с сохранением состояния.
Рисунок 3. Пример поведения, сохраняющего состояние.

В следующем примере кода показано, как реализовать эти компоненты.

Котлин

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true)

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2)

// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
        R.id.my_checkbox,
        RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent)
)

Java

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true);

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2);

// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
    R.id.my_checkbox,
    RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent));

Предоставьте два варианта оформления: один для устройств под управлением Android 12 или выше (папка res/layout-v31 , а другой для устройств под управлением Android 11 или ниже (папка res/layout по умолчанию).

Примените закругленные углы.

В Android 12 появились следующие системные параметры для установки радиусов скругления углов виджета:

  • system_app_widget_background_radius : радиус скругления углов фона виджета, который никогда не превышает 28 dp.

  • Внутренний радиус, который можно рассчитать, исходя из внешнего радиуса и отступа. См. следующий фрагмент кода:

    /**
     * Applies corner radius for views that are visually positioned [widgetPadding]dp inside of the
     * widget background.
     */
    @Composable
    fun GlanceModifier.appWidgetInnerCornerRadius(widgetPadding: Dp): GlanceModifier {
    
        if (Build.VERSION.SDK_INT < 31) {
            return this
        }
    
        val resources = LocalContext.current.resources
        // get dimension in float (without rounding).
        val px = resources.getDimension(android.R.dimen.system_app_widget_background_radius)
        val widgetBackgroundRadiusDpValue = px / resources.displayMetrics.density
        if (widgetBackgroundRadiusDpValue < widgetPadding.value) {
            return this
        }
        return this.cornerRadius(Dp(widgetBackgroundRadiusDpValue - widgetPadding.value))
    }

Для расчета подходящего радиуса для внутреннего содержимого вашего виджета используйте следующую формулу: systemRadiusValue - widgetPadding

Виджеты, которые обрезают свой контент по непрямоугольным формам, должны использовать @android:id/background в качестве идентификатора представления фонового элемента, у которого android:clipToOutline установлен в true .

Важные моменты, которые следует учитывать при обработке закругленных углов.

  • Сторонние лаунчеры и производители устройств могут изменить значение параметра system_app_widget_background_radius , установив его меньше 28 dp.
  • Если ваш виджет не использует @android:id/background или не определяет фон, который обрезает его содержимое по контуру (при этом android:clipToOutline установлено в true , то средство запуска автоматически определяет фон и обрезает виджет с помощью прямоугольника со скругленными углами, заданными системным радиусом.

  • Непрямоугольные фигуры необходимо помещать в контейнер для изменения размера, соответствующий их закругленному прямоугольному контуру, чтобы они не обрезались.

  • Начиная с Android 16, системное значение параметра system_app_widget_background_radius в AOSP составляет 24dp . Лаунчеры и производители устройств могут обрезать виджет до значения system_app_widget_background_radius .

  • Внутреннее содержимое виджета должно иметь достаточный отступ для поддержки значений радиуса system_app_widget_background_radius до 28dp чтобы избежать обрезки содержимого закругленными углами.

Для обеспечения совместимости виджетов с предыдущими версиями Android мы рекомендуем определить пользовательские атрибуты и использовать пользовательскую тему для их переопределения в Android 12, как показано в следующих примерах XML-файлов:

/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>

/layout/my_widget_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:background="@drawable/my_widget_background" />