Créer un widget simple

Les widgets d'application sont des vues d'application miniatures que vous pouvez intégrer à d'autres applications (comme l'écran d'accueil) et recevoir des mises à jour périodiques. Ces vues sont appelées widgets dans l'interface utilisateur. Vous pouvez en publier une avec un fournisseur de widgets d'application (ou fournisseur de widgets). Un composant d'application qui contient d'autres widgets est appelé hôte de widget d'application (ou hôte de widget). La figure 1 illustre un exemple de widget Musique:

Exemple de widget Musique
Figure 1. Exemple de widget Musique

Ce document explique comment publier un widget à l'aide d'un fournisseur de widgets. Pour en savoir plus sur la création de votre propre AppWidgetHost pour héberger des widgets d'application, consultez Créer un hôte de widget.

Pour en savoir plus sur la conception de votre widget, consultez Présentation des widgets d'application.

Composants du widget

Pour créer un widget, vous avez besoin des composants de base suivants:

Objet AppWidgetProviderInfo
Décrit les métadonnées d'un widget, telles que sa mise en page, sa fréquence de mise à jour et sa classe AppWidgetProvider. AppWidgetProviderInfo est défini en XML, comme décrit dans ce document.
Classe AppWidgetProvider
Définit les méthodes de base vous permettant de programmer l'interface avec le widget. Elle vous permet de recevoir des annonces lorsque le widget est mis à jour, activé, désactivé ou supprimé. Déclarez AppWidgetProvider dans le fichier manifeste, puis implémentez-le, comme décrit dans ce document.
Afficher la mise en page
Définit la mise en page initiale du widget. La mise en page est définie au format XML, comme décrit dans ce document.

La figure 2 montre comment ces composants s'intègrent dans le flux global de traitement des widgets d'application.

Flux de traitement du widget d'application
Figure 2 : Flux de traitement des widgets d'application

Si votre widget nécessite une configuration utilisateur, implémentez l'activité de configuration du widget d'application. Cette activité permet aux utilisateurs de modifier les paramètres du widget, par exemple le fuseau horaire d'un widget d'horloge.

Nous vous recommandons également les améliorations suivantes: mises en page flexibles des widgets, différentes améliorations, widgets avancés, widgets de collection et création d'un hôte de widgets.

Déclarer le fichier XML AppWidgetProviderInfo

L'objet AppWidgetProviderInfo définit les qualités essentielles d'un widget. Définissez l'objet AppWidgetProviderInfo dans un fichier de ressources XML à l'aide d'un seul élément <appwidget-provider>, puis enregistrez-le dans le dossier res/xml/ du projet.

Ce processus est illustré dans l'exemple suivant :

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

Attributs de dimensionnement des widgets

L'écran d'accueil par défaut positionne les widgets dans sa fenêtre en fonction d'une grille de cellules d'une hauteur et d'une largeur définies. La plupart des écrans d'accueil ne permettent aux widgets de prendre que des tailles qui sont des multiples entiers des cellules de la grille (par exemple, deux cellules horizontalement par trois cellules verticalement).

Les attributs de dimensionnement de widget vous permettent de spécifier une taille par défaut pour votre widget, et de définir des limites inférieure et supérieure pour sa taille. Dans ce contexte, la taille par défaut d'un widget correspond à la taille qu'il prend lorsqu'il est ajouté pour la première fois à l'écran d'accueil.

Le tableau suivant décrit les attributs <appwidget-provider> relatifs au dimensionnement du widget:

Attributs et description
targetCellWidth et targetCellHeight (Android 12), minWidth et minHeight
  • À partir d'Android 12, les attributs targetCellWidth et targetCellHeight spécifient la taille par défaut du widget en termes de cellules de grille. Ces attributs sont ignorés sur Android 11 et les versions antérieures, et peuvent être ignorés si l'écran d'accueil n'est pas compatible avec une mise en page basée sur une grille.
  • Les attributs minWidth et minHeight spécifient la taille par défaut du widget en dp. Si les valeurs de largeur ou de hauteur minimales d'un widget ne correspondent pas aux dimensions des cellules, elles sont arrondies à la taille de cellule la plus proche.
Nous vous recommandons de spécifier les deux ensembles d'attributs (targetCellWidth et targetCellHeight, ainsi que minWidth et minHeight) afin que votre application puisse utiliser minWidth et minHeight si l'appareil de l'utilisateur n'est pas compatible avec targetCellWidth et targetCellHeight. S'ils sont pris en charge, les attributs targetCellWidth et targetCellHeight sont prioritaires sur les attributs minWidth et minHeight.
minResizeWidth et minResizeHeight Spécifiez la taille minimale absolue du widget. Ces valeurs spécifient la taille en dessous de laquelle le widget est illisible ou inutilisable. L'utilisation de ces attributs permet à l'utilisateur de redimensionner le widget pour qu'il soit inférieur à la taille par défaut du widget. L'attribut minResizeWidth est ignoré s'il est supérieur à minWidth ou si le redimensionnement horizontal n'est pas activé. Consultez resizeMode. De même, l'attribut minResizeHeight est ignoré s'il est supérieur à minHeight ou si le redimensionnement vertical n'est pas activé.
maxResizeWidth et maxResizeHeight Spécifiez la taille maximale recommandée pour le widget. Si les valeurs ne sont pas un multiple des dimensions des cellules de la grille, elles sont arrondies à la taille de cellule la plus proche. L'attribut maxResizeWidth est ignoré s'il est inférieur à minWidth ou si le redimensionnement horizontal n'est pas activé. Voir resizeMode. De même, l'attribut maxResizeHeight est ignoré s'il est supérieur à minHeight ou si le redimensionnement vertical n'est pas activé. Introduit dans Android 12.
resizeMode Spécifie les règles de redimensionnement d'un widget. Vous pouvez utiliser cet attribut pour rendre les widgets de l'écran d'accueil redimensionnables horizontalement, verticalement ou sur les deux axes. Les utilisateurs appuient de manière prolongée sur un widget pour afficher ses poignées de redimensionnement, puis font glisser les poignées horizontales ou verticales pour modifier sa taille dans la grille de mise en page. Les valeurs de l'attribut resizeMode incluent horizontal, vertical et none. Pour déclarer un widget comme redimensionnable horizontalement et verticalement, utilisez horizontal|vertical.

Exemple

Pour illustrer la façon dont les attributs du tableau précédent affectent le dimensionnement du widget, supposons que vous respectiez les spécifications suivantes:

  • Une cellule de grille fait 30 dp de large et 50 dp de haut.
  • Les spécifications d'attribut suivantes sont fournies:
<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" />

À partir d'Android 12:

Utilisez les attributs targetCellWidth et targetCellHeight comme taille par défaut du widget.

Par défaut, la taille du widget est de 2 x 2. Le widget peut être redimensionné jusqu'à 2 x 1 ou jusqu'à 4 x 3.

Android 11 ou version antérieure:

Utilisez les attributs minWidth et minHeight pour calculer la taille par défaut du widget.

Largeur par défaut = Math.ceil(80 / 30) = 3

Hauteur par défaut = Math.ceil(80 / 50) = 2

Par défaut, la taille du widget est de 3 x 2. Le widget peut être redimensionné jusqu'à 2 x 1 ou en plein écran.

Attributs de widget supplémentaires

Le tableau suivant décrit les attributs <appwidget-provider> relatifs aux qualités autres que le dimensionnement du widget.

Attributs et description
updatePeriodMillis Définit la fréquence à laquelle le framework du widget demande une mise à jour à partir de AppWidgetProvider en appelant la méthode de rappel onUpdate(). Il n'est pas garanti que la mise à jour se produise exactement à l'heure avec cette valeur. Nous vous recommandons donc d'effectuer une mise à jour aussi peu fréquemment que possible (pas plus d'une fois par heure) pour économiser la batterie. Pour obtenir la liste complète des éléments à prendre en compte pour choisir une période de mise à jour appropriée, consultez la section Optimisations pour la mise à jour du contenu des widgets.
initialLayout Pointe vers la ressource de mise en page qui définit la mise en page du widget.
configure Définit l'activité qui se lance lorsque l'utilisateur ajoute le widget, ce qui lui permet de configurer les propriétés du widget. Consultez Autoriser les utilisateurs à configurer des widgets. À partir d'Android 12, votre application peut ignorer la configuration initiale. Pour en savoir plus, consultez Utiliser la configuration par défaut du widget.
description Spécifie la description que le sélecteur de widgets doit afficher pour votre widget. Introduit dans Android 12.
previewLayout (Android 12) et previewImage (Android 11 ou version antérieure)
  • À partir d'Android 12, l'attribut previewLayout spécifie un aperçu évolutif, que vous fournissez sous forme de mise en page XML définie sur la taille par défaut du widget. Idéalement, le fichier XML de mise en page spécifié dans cet attribut est le même que celui du widget réel, avec des valeurs par défaut réalistes.
  • Sous Android 11 ou version antérieure, l'attribut previewImage spécifie un aperçu de ce à quoi ressemblera le widget après sa configuration. Ce que l'utilisateur voit lorsqu'il sélectionne le widget d'application. Si elle n'est pas fournie, l'utilisateur voit à la place l'icône de lanceur de votre application. Ce champ correspond à l'attribut android:previewImage de l'élément <receiver> du fichier AndroidManifest.xml.
Remarque:Nous vous recommandons de spécifier les attributs previewImage et previewLayout afin que votre application puisse utiliser previewImage si l'appareil de l'utilisateur n'est pas compatible avec previewLayout. Pour en savoir plus, consultez la section Rétrocompatibilité avec les aperçus de widgets évolutifs.
autoAdvanceViewId Spécifie l'ID de la sous-vue du widget avancée automatiquement par l'hôte du widget.
widgetCategory Indique si votre widget peut être affiché sur l'écran d'accueil (home_screen), sur l'écran de verrouillage (keyguard) ou sur les deux. Pour Android 5.0 ou version ultérieure, seul home_screen est valide.
widgetFeatures Déclare les fonctionnalités compatibles avec le widget. Par exemple, si vous souhaitez que votre widget utilise sa configuration par défaut lorsqu'un utilisateur l'ajoute, spécifiez les options configuration_optional et reconfigurable. Cela permet de contourner le lancement de l'activité de configuration après qu'un utilisateur a ajouté le widget. L'utilisateur peut toujours reconfigurer le widget par la suite.

Utiliser la classe AppWidgetProvider pour gérer les diffusions de widget

La classe AppWidgetProvider gère les diffusions de widget et le met à jour en réponse aux événements de cycle de vie du widget. Les sections suivantes décrivent comment déclarer AppWidgetProvider dans le fichier manifeste, puis l'implémenter.

Déclarer un widget dans le fichier manifeste

Tout d'abord, déclarez la classe AppWidgetProvider dans le fichier AndroidManifest.xml de votre application, comme illustré dans l'exemple suivant:

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

L'élément <receiver> nécessite l'attribut android:name, qui spécifie la AppWidgetProvider utilisée par le widget. Le composant ne doit pas être exporté, sauf si un processus distinct doit être diffusé sur votre AppWidgetProvider, ce qui n'est généralement pas le cas.

L'élément <intent-filter> doit inclure un élément <action> avec l'attribut android:name. Cet attribut spécifie que AppWidgetProvider accepte la diffusion ACTION_APPWIDGET_UPDATE. Il s'agit de la seule annonce que vous devez déclarer explicitement. AppWidgetManager envoie automatiquement toutes les autres diffusions de widget au AppWidgetProvider, si nécessaire.

L'élément <meta-data> spécifie la ressource AppWidgetProviderInfo et nécessite les attributs suivants:

  • android:name: spécifie le nom des métadonnées. Utilisez android.appwidget.provider pour identifier les données en tant que descripteur AppWidgetProviderInfo.
  • android:resource: spécifie l'emplacement de la ressource AppWidgetProviderInfo.

Implémenter la classe AppWidgetProvider

La classe AppWidgetProvider étend BroadcastReceiver en tant que classe pratique pour gérer les annonces de widget. Il ne reçoit que les diffusions d'événements pertinentes pour le widget, par exemple lorsque le widget est mis à jour, supprimé, activé ou désactivé. Lorsque ces événements de diffusion se produisent, les méthodes AppWidgetProvider suivantes sont appelées:

onUpdate()
Cette opération est appelée pour mettre à jour le widget à des intervalles définis par l'attribut updatePeriodMillis dans AppWidgetProviderInfo. Pour en savoir plus, consultez le tableau décrivant les autres attributs de widget sur cette page.
Cette méthode est également appelée lorsque l'utilisateur ajoute le widget. Elle effectue la configuration essentielle, comme la définition de gestionnaires d'événements pour les objets View ou le démarrage de tâches pour charger des données à afficher dans le widget. Toutefois, si vous déclarez une activité de configuration sans l'option configuration_optional, cette méthode n'est pas appelée lorsque l'utilisateur ajoute le widget, mais est appelée pour les mises à jour ultérieures. Il appartient à l'activité de configuration d'effectuer la première mise à jour une fois la configuration terminée. Pour en savoir plus, consultez Permettre aux utilisateurs de configurer des widgets d'application.
Le rappel le plus important est onUpdate(). Consultez la section Gérer les événements avec la classe onUpdate() sur cette page pour en savoir plus.
onAppWidgetOptionsChanged()

Cette méthode est appelée lorsque le widget est placé pour la première fois et chaque fois qu'il est redimensionné. Utilisez ce rappel pour afficher ou masquer le contenu en fonction des plages de tailles du widget. Obtenez les plages de tailles (et, à partir d'Android 12, la liste des tailles possibles d'une instance de widget), en appelant getAppWidgetOptions(), qui renvoie un Bundle qui inclut les éléments suivants:

onDeleted(Context, int[])

Cette méthode est appelée chaque fois qu'un widget est supprimé de l'hôte du widget.

onEnabled(Context)

Cette méthode est appelée lorsqu'une instance du widget est créée pour la première fois. Par exemple, si l'utilisateur ajoute deux instances de votre widget, cette action n'est appelée que la première fois. Si vous devez ouvrir une nouvelle base de données ou effectuer une autre configuration qui ne doit être effectuée qu'une seule fois pour toutes les instances de widget, c'est l'endroit idéal pour le faire.

onDisabled(Context)

Cette méthode est appelée lorsque la dernière instance de votre widget est supprimée de l'hôte du widget. C'est ici que vous nettoyez toutes les tâches effectuées dans onEnabled(Context), comme la suppression d'une base de données temporaire.

onReceive(Context, Intent)

Cette méthode est appelée à chaque diffusion et avant chacune des méthodes de rappel précédentes. Normalement, vous n'avez pas besoin d'implémenter cette méthode, car l'implémentation par défaut de AppWidgetProvider filtre toutes les diffusions de widget et appelle les méthodes précédentes selon les cas.

Vous devez déclarer l'implémentation de la classe AppWidgetProvider en tant que broadcast receiver à l'aide de l'élément <receiver> dans AndroidManifest. Pour en savoir plus, consultez la section Déclarer un widget dans le fichier manifeste sur cette page.

Gérer les événements avec la classe onUpdate()

Le rappel AppWidgetProvider le plus important est onUpdate(), car il est appelé lorsque chaque widget est ajouté à un hôte, sauf si vous utilisez une activité de configuration sans l'option configuration_optional. Si votre widget accepte tous les événements d'interaction utilisateur, enregistrez les gestionnaires d'événements dans ce rappel. Si votre widget ne crée pas de fichiers ni de bases de données temporaires ni n'effectue d'autres tâches qui nécessitent un nettoyage, onUpdate() peut être la seule méthode de rappel à définir.

Par exemple, si vous souhaitez un widget avec un bouton qui lance une activité lorsque l'utilisateur appuie dessus, vous pouvez utiliser l'implémentation suivante de AppWidgetProvider:

Kotlin

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);
        }
    }
}

Ce AppWidgetProvider définit uniquement la méthode onUpdate() et l'utilise pour créer un PendingIntent qui lance une Activity et l'associe au bouton du widget à l'aide de setOnClickPendingIntent(int, PendingIntent). Il inclut une boucle qui parcourt chaque entrée de appWidgetIds, qui est un tableau d'ID identifiant chaque widget créé par ce fournisseur. Si l'utilisateur crée plusieurs instances du widget, elles sont toutes mises à jour simultanément. Cependant, une seule planification updatePeriodMillis est gérée pour toutes les instances du widget. Par exemple, si le calendrier de mise à jour est défini sur une fréquence de deux heures et qu'une deuxième instance du widget est ajoutée une heure après la première, elles sont toutes les deux mises à jour sur la période définie par la première, et la seconde est ignorée. Elles se mettent à jour toutes les deux heures, et non toutes les heures.

Pour en savoir plus, consultez l'exemple de classe ExampleAppWidgetProvider.java.

Recevoir des intents de diffusion de widgets

AppWidgetProvider est une classe de commodité. Si vous souhaitez recevoir directement les diffusions du widget, vous pouvez implémenter votre propre BroadcastReceiver ou ignorer le rappel onReceive(Context,Intent). Voici les intents à prendre en compte:

Créer la mise en page de widget

Vous devez définir une mise en page initiale pour votre widget au format XML et l'enregistrer dans le répertoire res/layout/ du projet. Pour en savoir plus, consultez les consignes de conception.

La création de la mise en page du widget est simple si vous connaissez les mises en page. Toutefois, sachez que les mises en page de widgets sont basées sur RemoteViews, qui n'est pas compatible avec tous les types de mises en page ou de widgets de vue. Vous ne pouvez pas utiliser d'affichages personnalisés ni de sous-classes des vues compatibles avec RemoteViews.

RemoteViews est également compatible avec ViewStub, un View invisible de taille nulle que vous pouvez utiliser pour gonfler, de manière différée, des ressources de mise en page lors de l'exécution.

Prise en charge du comportement avec état

Android 12 prend en charge le comportement avec état à l'aide des composants existants suivants:

Le widget est toujours sans état. Votre application doit stocker l'état et s'inscrire aux événements de changement d'état.

Exemple de widget de liste de courses montrant un comportement avec état
Figure 3. Exemple de comportement avec état

L'exemple de code suivant montre comment implémenter ces composants.

Kotlin

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

Fournissez deux mises en page: l'une ciblant les appareils équipés d'Android 12 ou version ultérieure dans res/layout-v31, et l'autre ciblant l'ancienne version d'Android 11 ou une version antérieure dans le dossier res/layout par défaut.

Implémenter des angles arrondis

Android 12 introduit les paramètres système suivants pour définir les rayons des angles arrondis de votre widget:

  • system_app_widget_background_radius : rayon d'angle de l'arrière-plan du widget, qui n'est jamais supérieur à 28 dp.

  • system_app_widget_inner_radius : rayon d'angle de n'importe quelle vue à l'intérieur du widget. Cela correspond exactement à 8 dp de moins que le rayon de l'arrière-plan, pour un alignement optimal avec une marge intérieure de 8 dp.

L'exemple suivant montre un widget qui utilise system_app_widget_background_radius pour l'angle du widget et system_app_widget_inner_radius pour les vues à l'intérieur du widget.

Widget affichant les rayons de l&#39;arrière-plan du widget et ses vues
Figure 4. Coins arrondis

1 Angle du widget.

2 Angle d'une vue à l'intérieur du widget.

Remarques importantes concernant les angles arrondis

  • Les lanceurs d'applications et les fabricants d'appareils tiers peuvent remplacer le paramètre system_app_widget_background_radius pour qu'il soit inférieur à 28 dp. Le paramètre system_app_widget_inner_radius est toujours inférieur de 8 dp à la valeur de system_app_widget_background_radius.
  • Si votre widget n'utilise pas @android:id/background ou ne définit pas d'arrière-plan qui rogne son contenu en fonction du contour (avec android:clipToOutline défini sur true), le lanceur identifie automatiquement l'arrière-plan et rogne le widget à l'aide d'un rectangle dont les angles arrondis peuvent atteindre 16 dp. Consultez Vérifier que votre widget est compatible avec Android 12.

Pour assurer la compatibilité des widgets avec les versions précédentes d'Android, nous vous recommandons de définir des attributs personnalisés et d'utiliser un thème personnalisé pour les remplacer pour Android 12, comme indiqué dans les exemples de fichiers XML suivants:

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