Glance でアプリ ウィジェットを作成する

以降のセクションでは、Glance を使用して基本的なアプリ ウィジェットを作成する方法について説明します。

マニフェストで AppWidget を宣言する

設定手順を完了したら、アプリで AppWidget とそのメタデータを宣言します。

  1. GlanceAppWidgetReceiver から AppWidget レシーバーを拡張します。

    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 の唯一の違いは、initialLayout XML がないことですが、定義する必要があります。ライブラリに用意されている事前定義の読み込みレイアウトを使用できます。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

AppWidgetProviderInfo XML を宣言する

AppWidgetProviderInfo オブジェクトは、ウィジェットの重要な特性を定義します。XML メタデータ リソース ファイル(res/xml/my_app_widget_info.xml)の <appwidget-provider> 要素内で AppWidgetProviderInfo を定義します。

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

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

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

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

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

属性と説明
targetCellWidth、targetCellHeight(Android 12)、minWidth、minHeight
  • Android 12 以降では、targetCellWidth 属性と targetCellHeight 属性で、ウィジェットのデフォルト サイズをグリッドセル単位で指定します。これらの属性は、Android 11 以前では無視されます。また、ホーム画面がグリッドベースのレイアウトをサポートしていない場合も無視できます。
  • minWidth 属性と minHeight 属性は、ウィジェットのデフォルト サイズを dp 単位で指定します。ウィジェットの最小幅または最小高さの値がセルのサイズと一致しない場合、値は最も近いセルサイズに切り上げられます。
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 属性をウィジェットのデフォルト サイズとして使用します。

ウィジェットのデフォルトのサイズは 2x2 です。ウィジェットのサイズは、2x1 まで縮小、4x3 まで拡大できます。

Android 11 以前:

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

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

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

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

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

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

属性と説明
updatePeriodMillis ウィジェット フレームワークが onUpdate() コールバック メソッドを呼び出して GlanceAppWidgetReceiver からの更新をリクエストする頻度を定義します。バッテリーを節約するため、更新頻度はできるだけ低く(1 時間に 1 回以下)することをおすすめします。詳細については、Glance の状態管理の ウィジェットを更新するタイミング セクションを参照してください。
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 以降)では、`GlanceAppWidget.providePreview` を使用して Kotlin でライブ生成プレビューを定義できます。生成されたプレビューのガイドをご覧ください。
autoAdvanceViewId ウィジェットのホストによって自動的に進められるウィジェット サブビューのビュー ID を指定します。
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. GlanceAppWidgetReceiver の glanceAppWidget でインスタンス化します。

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

これで、Glance を使用して AppWidget を構成できました。

サイズバケット全体でウィジェットの画像メモリを管理する

ウィジェットにビットマップまたは content:// 画像 URI を表示する場合は、次の方法で、シリアル化された RemoteViews 画像メモリの合計をシステムの上限(6 * width * height バイト、Android 17 以降では IllegalArgumentException で適用)内に収めます。

  • provideContent の前に共有ビットマップをデコードする: ウィジェットで SizeMode.Responsive または SizeMode.Exact を使用している場合、Glance は DpSize バケットごとに provideContent ラムダを 1 回実行します。provideContent 内で Bitmap をデコードすると、バケットごとに個別の Bitmap インスタンスが割り当てられ、RemoteViews.mBitmapCache のメモリ使用量が増加します。provideContent の前に provideGlance 内でダウンサンプリングされたビットマップ(不透明な画像の場合は Bitmap.Config.RGB_565)を 1 回デコードして、すべてのバケットが単一の Bitmap 参照を共有するようにします。
  • 一致する ImageProvider インポートを使用する: Jetpack Glance には、2 つのパッケージにわたって 2 つの異なる ImageProvider オーバーロードが用意されています。

    • メモリ内 ImageProvider(bitmap: Bitmap) またはドローアブル リソース ImageProvider(resId: Int) を渡す場合は、import androidx.glance.ImageProvider を使用します。
    • URI ImageProvider(uri: Uri) を渡す場合は import androidx.glance.appwidget.ImageProvider を使用します。FileProvider URI を介してキャッシュに保存されたファイルを提供する場合は、インストールされているすべての Intent.CATEGORY_HOME ランチャー パッケージに Intent.FLAG_GRANT_READ_URI_PERMISSION を付与して、ホストが画像をレンダリングできるようにします。

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

GlanceAppWidgetReceiver 座標ウィジェットは、基盤となる AppWidgetProvider を拡張することで、ブロードキャストとプラットフォームの状態の更新を行います。ウィジェットが更新、削除、有効化、無効化されたときにプラットフォーム イベントを受信し、Compose ライフサイクル リクエストに変換します。

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

AndroidManifest.xml ファイルで、GlanceAppWidgetReceiver クラスのサブクラスをブロードキャスト レシーバとして宣言します。

<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 属性が必要です。レシーバは、<intent-filter> 内で ACTION_APPWIDGET_UPDATE ブロードキャスト アクションを受け入れる必要があります。

<meta-data> 要素は、その名前を android.appwidget.provider として識別する必要があります。また、android:resource 属性は、AppWidgetProviderInfo XML メタデータ リソース(@xml/my_app_widget_info)を指す必要があります。

GlanceAppWidgetReceiver クラスを実装する

Glance では、AppWidgetProvider を直接拡張するのではなく、GlanceAppWidgetReceiver を拡張します。これを実装するには、レシーバーを GlanceAppWidget インスタンスにリンクします。GlanceAppWidgetReceiver で使用できる主なコールバックは、次のように動作します。

  • Glance によって自動的にオーバーライドされ、コンポジションの更新が実行されます。onUpdate()onUpdate を手動でオーバーライドする場合は、super.onUpdate を呼び出して、Glance がコンポジション スレッドを正常に起動できるようにする必要があります。
  • onAppWidgetOptionsChanged(): ウィジェットが最初に配置またはサイズ変更されたときに呼び出されます。Glance は、内部でオプション バンドル アイテムを読み取るため、ランタイム ディメンションに基づいてレイアウトがシームレスに調整されます。
  • onDeleted(Context, IntArray): ユーザーが特定のウィジェット インスタンスを削除するたびに呼び出されます。
  • onEnabled(Context): ウィジェットの最初のインスタンスが正常に作成されたときにトリガーされます。グローバルな移行の実行に最適です。
  • onDisabled(Context): プロバイダの最後のアクティブなインスタンスが削除されたときに呼び出されます。
  • onReceive(Context, Intent): 特定のコールバック メソッドの前に、すべてのプラットフォーム ブロードキャストをインターセプトします。作成するカスタム レシーバ ロジックが super.onReceive(context, intent) を呼び出すようにする必要があります。Glance は作業を非同期で自動的にルーティングするため、goAsync を自分で呼び出すことはできません。

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

内部的には、GlanceAppWidgetReceiver は次の基本的なプラットフォーム ウィジェット ブロードキャスト インテントをフィルタして処理します。

UI を作成する

次のスニペットは、UI を作成する方法を示しています。

/* 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 では、アイテムが縦方向に 1 つずつ配置されます。
  • Column は、利用可能なスペースに合わせてサイズを拡大し(GlanceModifier を使用)、コンテンツを上部に配置し(verticalAlignment)、水平方向に中央揃えします(horizontalAlignment)。
  • Column のコンテンツはラムダを使用して定義されます。ここで重要となるのは順序です。
    • Column の最初のアイテムは、12.dp のパディングを含む Text コンポーネントです。
    • 2 つ目のアイテムは Row で、アイテムが水平方向に並べられ、2 つの Buttons が水平方向に中央揃えされます(horizontalAlignment)。最終的な表示は、使用可能なスペースによって異なります。次の画像は、その例です。
destination_widget
図 1. UI の例。

配置値を変更したり、異なる修飾子値(パディングなど)を適用したりして、コンポーネントの配置とサイズを変更できます。各クラスのコンポーネント、パラメータ、使用可能な修飾子の完全なリストについては、リファレンス ドキュメントをご覧ください。

角の丸みを実装する

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>