シンプルなウィジェットを作成する

アプリ ウィジェットとは、他のアプリ(ホーム画面など)に埋め込んで定期的に更新を取得することができる小さなアプリビューです。このようなビューをユーザー インターフェースではウィジェットと呼び、ウィジェットはアプリ ウィジェット プロバイダ(またはウィジェット プロバイダ)を使用して公開できます。他のウィジェットを保持するアプリ コンポーネントをアプリ ウィジェット ホスト(またはウィジェット ホスト)と呼びます。図 1 に音楽ウィジェットの例を示します。

音楽ウィジェットの例
図 1. 音楽ウィジェットの例。

このドキュメントでは、ウィジェット プロバイダを使用してウィジェットを公開する方法について説明します。アプリ ウィジェットをホストする独自の AppWidgetHost を作成する方法については、ウィジェット ホストを作成するをご覧ください。

ウィジェットの設計方法については、アプリ ウィジェットの概要をご覧ください。

ウィジェット コンポーネント

ウィジェットを作成するには、次の基本コンポーネントが必要です。

AppWidgetProviderInfo オブジェクト
ウィジェットのメタデータ(ウィジェットのレイアウト、更新頻度、AppWidgetProvider クラスなど)を記述します。AppWidgetProviderInfo は、このドキュメントで説明されているように、XML で定義されています。
AppWidgetProvider クラス
ウィジェットをプログラムで操作するための基本的なメソッドを定義します。ウィジェットが更新、有効化、無効化、削除されたときにこのクラスを介してブロードキャストが届きます。マニフェストで AppWidgetProvider を宣言し、このドキュメントで説明するように実装します。
ビューのレイアウト
ウィジェットの初期レイアウトを定義します。レイアウトは、このドキュメントで説明されているように、XML で定義されます。

図 2 は、これらのコンポーネントがアプリ ウィジェットの全体的な処理フローにどのように組み込まれるかを示しています。

アプリ ウィジェットの処理フロー
図 2. アプリ ウィジェットの処理フロー。

ウィジェットにユーザー設定が必要な場合は、アプリ ウィジェット設定アクティビティを実装します。このアクティビティでは、ユーザーがウィジェットの設定(時計ウィジェットのタイムゾーンなど)を変更できます。

また、柔軟なウィジェット レイアウトその他の機能強化高度なウィジェットコレクション ウィジェットウィジェット ホストの作成などの改善もおすすめします。

AppWidgetProviderInfo XML を宣言する

AppWidgetProviderInfo オブジェクトは、ウィジェットの基本的な特性を定義します。単一の <appwidget-provider> 要素を使用して XML リソース ファイルで AppWidgetProviderInfo オブジェクトを定義し、プロジェクトの 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>

ウィジェットのサイズ設定属性

デフォルトのホーム画面では、ウィジェットは高さと幅が定義されたセルのグリッドに基づいてウィンドウ内に配置されます。ほとんどのホーム画面では、ウィジェットのサイズはグリッドセルの整数倍にする必要があります(例: 横方向に 2 セル、縦方向に 3 セル)。

ウィジェットのサイズ設定属性を使用すると、ウィジェットのデフォルトのサイズを指定したり、ウィジェットのサイズの最小値と最大値を指定したりできます。このコンテキストでは、ウィジェットのデフォルト サイズとは、ウィジェットがホーム画面に初めて追加されたときにウィジェットが取得するサイズです。

次の表に、ウィジェットのサイズ設定に関連する <appwidget-provider> 属性を示します。

属性と説明
targetCellWidthtargetCellHeight(Android 12)、minWidthminHeight
  • Android 12 以降、targetCellWidth 属性と targetCellHeight 属性は、グリッドセル単位でウィジェットのデフォルトサイズを指定します。これらの属性は Android 11 以前では無視されます。また、ホーム画面がグリッドベースのレイアウトをサポートしていない場合は、無視することもできます。
  • minWidth 属性と minHeight 属性は、ウィジェットのデフォルト サイズを dp で指定します。ウィジェットの最小の幅または高さの値がセルのサイズと一致しない場合、値は最も近いセルサイズに切り上げられます。
targetCellWidthtargetCellHeightminWidthminHeight の両方の属性セットを指定することをおすすめします。これにより、ユーザーのデバイスが targetCellWidthtargetCellHeight をサポートしていない場合に、アプリが minWidthminHeight にフォールバックできます。サポートされている場合、targetCellWidth 属性と targetCellHeight 属性は、minWidth 属性と minHeight 属性よりも優先されます。
minResizeWidthminResizeHeight ウィジェットの絶対最小サイズを指定します。これらの値は、ウィジェットが判読不能または使用不能にならない範囲の最小サイズとする必要があります。これらの属性を使用すると、ユーザーはウィジェットのサイズをデフォルトのウィジェット サイズよりも小さいサイズに変更できます。minResizeWidth 属性が minWidth より大きい場合、または横のサイズ変更が有効になっていない場合、この属性は無視されます。resizeMode をご覧ください。同様に、minResizeHeight 属性が minHeight より大きい場合や、縦のサイズ変更が有効になっていない場合、この属性は無視されます。
maxResizeWidthmaxResizeHeight ウィジェットの推奨最大サイズを指定します。値がグリッドセルのサイズの倍数でない場合、最も近いセルサイズに切り上げられます。maxResizeWidth 属性が minWidth より小さい場合、または横のサイズ変更が有効になっていない場合、この属性は無視されます。resizeMode をご覧ください。同様に、maxResizeHeight 属性が minHeight より大きい場合、または縦のサイズ変更が有効になっていない場合、maxResizeHeight 属性は無視されます。Android 12 で導入されました。
resizeMode ウィジェットのサイズ変更に関するルールを指定します。この属性を使用すると、ホーム画面ウィジェットの縦、横、またはその両方のサイズを変更できます。ユーザーがウィジェットを長押しするとサイズ変更ハンドルが表示され、縦または横のハンドルをドラッグしてレイアウト グリッド上でサイズを変更できるようになります。resizeMode 属性の値には、horizontalverticalnone があります。縦と横の両方のサイズを変更できるウィジェットを宣言するには、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 です。ウィジェットのサイズは 2x1 から 4x3 まで変更できます。

Android 11 以前:

minWidth 属性と minHeight 属性を使用して、ウィジェットのデフォルト サイズを計算します。

デフォルトの幅 = Math.ceil(80 / 30) = 3

デフォルトの高さ = Math.ceil(80 / 50) = 2

ウィジェットのサイズはデフォルトで 3x2 です。ウィジェットのサイズは、2x1 まで小さくしたり、全画面表示まで大きくしたりできます。

その他のウィジェット属性

次の表に、ウィジェットのサイズ設定以外の品質に関連する <appwidget-provider> 属性を示します。

属性と説明
updatePeriodMillis ウィジェット フレームワークが onUpdate() コールバック メソッドを呼び出して AppWidgetProvider に更新をリクエストする頻度を定義します。実際の更新がこの値で正確に行われることを保証するものではありません。電池を節約するため、できるだけ低い頻度(たとえば 1 時間に 1 回以下)に設定することをおすすめします。適切な更新期間を選択する際の考慮事項の一覧については、ウィジェット コンテンツの更新を最適化するをご覧ください。
initialLayout ウィジェット レイアウトを定義するレイアウト リソースを指定します。
configure ユーザーがウィジェットを追加したときに起動して、ウィジェット プロパティを設定できるようにするアクティビティを定義します。ユーザーがウィジェットを設定できるようにするをご覧ください。Android 12 以降では、アプリは初期構成をスキップできます。詳しくは、ウィジェットのデフォルト設定を使用するをご覧ください。
description ウィジェットに表示するウィジェット選択ツールの説明を指定します。Android 12 で導入されました。
previewLayout(Android 12)および previewImage(Android 11 以前)
  • Android 12 以降では、previewLayout 属性でスケーラブルなプレビューを指定します。このプレビューは、ウィジェットのデフォルトのサイズに設定された XML レイアウトとして提供します。理想的には、この属性として指定されたレイアウト XML は、現実的なデフォルト値を持つ実際のウィジェットと同じレイアウト XML です。
  • Android 11 以前では、previewImage 属性は、ウィジェットの設定後にユーザーがアプリ ウィジェットを選択したときに表示されるウィジェットのプレビューを指定します。この属性が指定されていない場合は、アプリのランチャー アイコンが表示されます。このフィールドは、AndroidManifest.xml ファイル内の <receiver> 要素の android:previewImage 属性に対応します。
注: previewImagepreviewLayout の両方の属性を指定することをおすすめします。これにより、ユーザーのデバイスが previewLayout をサポートしていない場合に、アプリが previewImage を使用するようにフォールバックできます。詳しくは、スケーラブルなウィジェット プレビューに関する下位互換性をご覧ください。
autoAdvanceViewId ウィジェットのホストによって自動進行されるウィジェット サブビューのビュー ID を指定します。
widgetCategory ウィジェットをホーム画面(home_screen)、ロック画面(keyguard)、またはその両方に表示できるかどうかを宣言します。Android 5.0 以上では home_screen のみ有効です。
widgetFeatures ウィジェットでサポートされている機能を宣言します。たとえば、ユーザーがウィジェットを追加するときにデフォルト設定が使用されるようにしたい場合は、configuration_optional フラグと reconfigurable フラグの両方を指定します。これにより、ユーザーがウィジェットを追加した後の設定アクティビティの起動が省略されます。ユーザーは後でウィジェットを再設定できます。

AppWidgetProvider クラスを使用してウィジェットのブロードキャストを処理する

AppWidgetProvider クラスはウィジェット ブロードキャストを処理し、ウィジェットのライフサイクル イベントに応答してウィジェットを更新します。以降のセクションでは、マニフェストで AppWidgetProvider を宣言して実装する方法について説明します。

マニフェストでウィジェットを宣言する

まず、次の例に示すように、アプリの AndroidManifest.xml ファイルで AppWidgetProvider クラスを宣言します。

<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 属性とともに含める必要があります。この属性は、AppWidgetProviderACTION_APPWIDGET_UPDATE ブロードキャストを受け入れることを指定します。明示的に宣言する必要があるブロードキャストはこれだけです。AppWidgetManager は、必要に応じて他のすべてのウィジェット ブロードキャストを自動的に AppWidgetProvider に送信します。

<meta-data> 要素は、AppWidgetProviderInfo リソースを指定し、次の属性を必要とします。

  • android:name: メタデータ名を指定します。android.appwidget.provider を使用してデータを AppWidgetProviderInfo 記述子として特定します。
  • android:resource: AppWidgetProviderInfo リソースの場所を指定します。

AppWidgetProvider クラスを実装する

AppWidgetProvider クラスは、ウィジェットのブロードキャストを処理するコンビニエンス クラスとして BroadcastReceiver を拡張します。ウィジェットの更新、削除、有効化、無効化など、ウィジェットに関連するイベント ブロードキャストのみを受け取ります。これらのブロードキャスト イベントが発生すると、次の AppWidgetProvider メソッドが呼び出されます。

onUpdate()
AppWidgetProviderInfoupdatePeriodMillis 属性で定義された間隔でウィジェットを更新するために呼び出されます。詳細については、このページのその他のウィジェット属性を説明する表をご覧ください。
このメソッドは、ユーザーがウィジェットを追加したときにも呼び出され、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)

ウィジェットのインスタンスが初めて作成されたときに呼び出されます。たとえば、ユーザーがウィジェットのインスタンスを 2 つ追加した場合は、初回のみ呼び出されます。新しいデータベースを開く必要がある場合や、その他ウィジェットのすべてのインスタンスについて計 1 回だけ必要なセットアップを行う必要がある場合は、このタイミングが適しています。

onDisabled(Context)

このメソッドは、ウィジェットの最後のインスタンスがウィジェット ホストから削除されたときに呼び出されます。onEnabled(Context) で行った作業がある場合はこのタイミングで消去します(一時データベースを削除するなど)。

onReceive(Context, Intent)

これは、ブロードキャストごとに、また上記の各コールバック メソッドの前に呼び出されます。デフォルトの AppWidgetProvider 実装では、すべてのウィジェット ブロードキャストをフィルタリングし、必要に応じて上記のメソッドを呼び出すため、通常はこのメソッドを実装する必要はありません。

AndroidManifest<receiver> 要素を使用して、AppWidgetProvider クラスの実装をブロードキャスト レシーバとして宣言する必要があります。詳しくは、このページのマニフェストでウィジェットを宣言するをご覧ください。

onUpdate() クラスでイベントを処理する

最も重要な AppWidgetProvider コールバックは onUpdate() です。各ウィジェットがホストに追加されたときに呼び出されるためです(configuration_optional フラグのない設定アクティビティを使用する場合を除く)。ウィジェットがユーザー操作イベントを受け入れる場合、このコールバックにイベント ハンドラを登録します。ウィジェットが一時ファイルやデータベースを作成したり、クリーンアップが必要な他の作業を行ったりすることがない場合は、onUpdate() のみを定義すれば足りる場合があります。

たとえば、タップするとアクティビティを起動するボタンのあるウィジェットを作成する場合は、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);
        }
    }
}

この AppWidgetProvideronUpdate() メソッドのみを定義し、これを使用して Activity を起動する PendingIntent を作成し、setOnClickPendingIntent(int, PendingIntent) を使用してウィジェットのボタンにアタッチします。appWidgetIds の各エントリで繰り返されるループがあることに注意してください。このプロバイダが作成した各ウィジェットを特定する ID の配列です。ユーザーがウィジェットの複数のインスタンスを作成した場合、それらはすべて同時に更新されます。ただし、ウィジェットのすべてのインスタンスについて 1 つの updatePeriodMillis スケジュールのみが管理されます。たとえば、更新スケジュールが 2 時間ごとに定義され、ウィジェットの 2 番目のインスタンスが最初のインスタンスの 1 時間後に追加された場合、両方のインスタンスは最初のインスタンスで定義された期間に更新され、2 番目の更新期間は無視されます。どちらも 1 時間ではなく 2 時間ごとに更新されます。

詳細については、ExampleAppWidgetProvider.java サンプルクラスをご覧ください。

ウィジェットのブロードキャスト インテントを受け取る

AppWidgetProvider はコンビニエンス クラスです。ウィジェットのブロードキャストを直接受け取るようにするには、独自の BroadcastReceiver を実装するか、onReceive(Context,Intent) コールバックをオーバーライドする方法があります。注意が必要なインテントは次のとおりです。

ウィジェット レイアウトを作成する

ウィジェットの初期レイアウトを XML で定義し、プロジェクトの res/layout/ ディレクトリに保存する必要があります。詳しくは、設計ガイドラインをご覧ください。

レイアウトに慣れているデベロッパーであれば、ウィジェットのレイアウトの作成は簡単に行えます。ただし、ウィジェットのレイアウトは RemoteViews をベースとしており、すべての種類のレイアウトやビューがサポートされているわけではないことに注意が必要です。RemoteViews でサポートされているカスタムビューやビューのサブクラスは使用できません。

RemoteViewsViewStub もサポートしています。これは、サイズがゼロの目に見えない View であり、実行時にレイアウト リソースを遅延インフレートするために使用できます。

ステートフル動作のサポート

Android 12 では、以下の既存のコンポーネントを使用したステートフル動作のサポートが追加されています。

このウィジェットはまだステートレスであるため、アプリが状態を保存し、状態変化イベントを登録できるようにする必要があります。

ステートフルな動作を示すショッピング リスト ウィジェットの例
図 3. ステートフルな動作の例。

次のコードサンプルは、これらのコンポーネントを実装する方法を示しています。

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

2 つのレイアウトを用意します。1 つは Android 12 以降を搭載しているデバイスをターゲットにしたレイアウト(res/layout-v31)で、もう 1 つは以前の Android 11 以前をターゲットにしたレイアウト(デフォルトの res/layout フォルダ内)です。

角の丸みを実装する

Android 12 では、次のシステム パラメータが導入され、ウィジェットの角の丸みを設定できるようになりました。

  • system_app_widget_background_radius: ウィジェットの背景の角の丸み。28 dp を超えることはありません。

  • system_app_widget_inner_radius: ウィジェット内のビューの角の丸み。8 dp のパディングを使用する際に適切に調整されるように、背景の角の丸みよりちょうど 8 dp 小さくなっています。

次の例は、system_app_widget_background_radius をウィジェットの角に、system_app_widget_inner_radius をウィジェット内のビューに使用したウィジェットを示しています。

ウィジェットの背景とウィジェット内のビューの半径を示すウィジェット
図 4. 角丸。

1 ウィジェットの角。

2 ウィジェット内のビューの角。

角を丸くする際の重要な考慮事項

  • サードパーティのランチャーとデバイス メーカーは、system_app_widget_background_radius パラメータをオーバーライドして 28 dp 未満にすることができます。system_app_widget_inner_radius パラメータは常に system_app_widget_background_radius の値より 8 dp 小さくなります。
  • ウィジェットで @android:id/background を使用していない場合や、アウトラインに基づいてコンテンツを切り抜く背景を定義していない場合(android:clipToOutlinetrue に設定されている場合)、ランチャーは背景を自動的に識別し、最大 16 dp の丸い角を持つ長方形を使用してウィジェットを切り抜きます。ウィジェットが Android 12 に対応していることを確認するをご覧ください。

ウィジェットを以前のバージョンの 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" />