柔軟なウィジェット レイアウトを提供する

Compose をお試しください
Jetpack Compose は、Android で推奨される UI ツールキットです。Compose スタイルの API を使用してウィジェットを構築する方法を学びます。

このページでは、Android 12(API レベル 31)で導入された、ウィジェットのサイズ設定の改良と柔軟性の向上について説明します。また、ウィジェットのサイズを決定する方法についても詳しく説明しています。

改良された API を使用してウィジェットのサイズとレイアウトを設定する

Android 12(API レベル 31)以降では、以下のセクションで説明するように、次の操作を行うことで、より洗練されたサイズ属性と柔軟なレイアウトを提供できます。

  1. ウィジェットのサイズに関する追加の制約を指定する。

  2. レスポンシブ レイアウトまたは正確なレイアウトを提供する。

以前のバージョンの Android では、OPTION_APPWIDGET_MIN_WIDTH、OPTION_APPWIDGET_MIN_HEIGHT、OPTION_APPWIDGET_MAX_WIDTH、OPTION_APPWIDGET_MAX_HEIGHT の各エクストラを使用してウィジェットのサイズ範囲を取得し、ウィジェットのサイズを推定できましたが、このロジックはすべての状況で機能するわけではありません。Android 12 以降をターゲットとするウィジェットの場合は、レスポンシブ レイアウトまたは正確なレイアウトを提供することをおすすめします。

ウィジェットのサイズ調整に関する追加の制約を指定する

Android 12 では、画面サイズが異なるさまざまなデバイスでウィジェットのサイズをより確実に調整できる API が追加されています。

既存の minWidth、minHeight、minResizeWidth、minResizeHeight 属性に加えて、次の新しい appwidget-provider 属性を使用します。

  • targetCellWidth と targetCellHeight: ランチャー グリッドセルの観点からウィジェットのターゲット サイズを定義します。定義されている場合は、minWidth や minHeight の代わりにこれらの属性が使用されます。

  • maxResizeWidth と maxResizeHeight: ランチャーがユーザーに許可するウィジェットのサイズ変更の上限を定義します。

次の XML は、サイズ設定属性の使用方法を示しています。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

レスポンシブ レイアウトを提供する

ウィジェットのサイズに応じてレイアウトを変更する必要がある場合は、さまざまなサイズに対して有効な、少数のレイアウトを作成することをおすすめしますこれが不可能な場合は、このページで説明するように、実行時の正確なウィジェット サイズに基づいてレイアウトを提供することもできます。

この機能により、システムはウィジェットを異なるサイズで表示するたびにアプリを起動する必要がなくなるため、スケーリングがスムーズになり、システム全体の健全性が向上します。

次のコードサンプルは、レイアウトのリストを提供する方法を示しています。

override fun onUpdate(
    /* ... */
) {
    val smallView = RemoteViews(
        /* ... */
    )
    val tallView = RemoteViews(
        /* ... */
    )
    val wideView = RemoteViews(
        /* ... */
    )

    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(150f, 100f) to smallView,
        SizeF(150f, 200f) to tallView,
        SizeF(215f, 100f) to wideView
    )
    val remoteViews = RemoteViews(viewMapping)

    appWidgetManager.updateAppWidget(id, remoteViews)
}

ウィジェットに次の属性があるとします。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

上記のコード スニペットは、次のことを意味します。

  • smallView は、160 dp(minResizeWidth)× 110 dp(minResizeHeight)から 160 dp × 199 dp(次のカットオフ ポイント - 1 dp)までをサポートします。
  • tallView は、160 dp × 200 dp から 214 dp(次のカットオフ ポイント - 1)× 200 dp までをサポートします。
  • wideView は、215 dp × 110 dp(minResizeHeight)から 250 dp(maxResizeWidth)× 200 dp(maxResizeHeight)までをサポートしています。

ウィジェットは、minResizeWidth × minResizeHeight から maxResizeWidth × maxResizeHeight までのサイズ範囲をサポートする必要があります。この範囲内で、レイアウトを切り替えるカットオフ ポイントを決定できます。

レスポンシブ レイアウトの例
図 1. レスポンシブ レイアウトの例。

正確なレイアウトを提供する

少数のレスポンシブ レイアウトを提供できない場合は、ウィジェットを表示するサイズに合わせてさまざまなレイアウトを提供できます。これは通常、スマートフォン用の 2 つのサイズ(縦向きと横向き)と、折りたたみ式デバイス用の 4 つのサイズで構成されます。

このソリューションを実装するには、アプリで次の手順を行う必要があります。

  1. サイズセットが変更されたときに呼び出される AppWidgetProvider.onAppWidgetOptionsChanged() をオーバーロードします。

  2. サイズを含む Bundle を返す AppWidgetManager.getAppWidgetOptions() を呼び出します。

  3. Bundle から AppWidgetManager.OPTION_APPWIDGET_SIZES キーにアクセスします。

次のコードサンプルは、正確なレイアウトを提供する方法を示しています。

override fun onAppWidgetOptionsChanged(
    context: Context,
    appWidgetManager: AppWidgetManager,
    id: Int,
    newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, id, newOptions)
    // Get the new sizes.
    val sizes = newOptions?.let {
        BundleCompat.getParcelableArrayList(
            it,
            AppWidgetManager.OPTION_APPWIDGET_SIZES,
            SizeF::class.java
        )
    }
    // Check that the list of sizes is provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    // Map the sizes to the RemoteViews that you want.
    val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
    appWidgetManager.updateAppWidget(id, remoteViews)
}

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews {
}

ウィジェットのサイズを決定する

各ウィジェットは、Android 12 以降を搭載したデバイスの場合は targetCellWidth と targetCellHeight、すべてのバージョンの Android の場合は minWidth と minHeight を定義して、デフォルトで使用する最小スペースを示す必要があります。ただし、ユーザーがホーム画面にウィジェットを追加すると、通常は指定した最小幅と最小高さよりも大きくなります。

Android のホーム画面には、ウィジェットやアイコンを配置できるグリッドが用意されています。このグリッドはデバイスによって異なります。たとえば、多くのスマートフォンでは 5x4 のグリッドが提供され、タブレットではより大きなグリッドが提供されます。ウィジェットが追加されると、Android 12 以降を搭載したデバイスでは targetCellWidth と targetCellHeight の制約、Android 11(API レベル 30)以前を搭載したデバイスでは minWidth と minHeight の制約を満たすために必要な最小限のセル数(水平方向と垂直方向)を占めるように引き伸ばされます。

セルの寸法と自動マージンはデバイスによって異なります。次の表を使用して、一般的な 5x4 グリッドのスマートフォンで、占有するグリッドセルの数に基づいてウィジェットの最小サイズを推定します。

セルの数(幅 x 高さ) 縦向きモードで使用可能なサイズ(dp) 横表示で使用可能なサイズ(dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
... ... ...
n × m (73n - 16)x(118m - 16) (142n - 15) x (66m - 15)

minWidth、minResizeWidth、maxResizeWidth 属性に指定する値は、ポートレート モードのセルサイズに基づいて決定します。同様に、横表示モードのセルサイズを使用して、minHeight、minResizeHeight、maxResizeHeight 属性に指定する値を決定します。

これは、通常、セルの幅は横表示よりも縦表示の方が小さく、同様に、セルの高さは縦表示よりも横表示の方が小さいためです。

たとえば、Google Pixel 4 でウィジェットの幅を 1 セルまでサイズ変更できるようにするには、minResizeWidth 属性の値を 57 dp 未満にするために、minResizeWidth を 56 dp 以下に設定する必要があります。これは、縦向きのセルの幅が 57 dp 以上であるためです。同様に、同じデバイスの 1 つのセルでウィジェットの高さをサイズ変更可能にする場合は、minResizeHeight 属性の値を 51 dp 未満にするために、minResizeHeight を 50 dp 以下に設定する必要があります。これは、横表示モードでは 1 つのセルの高さが 51 dp 以上になるためです。

各ウィジェットは、minResizeWidth/minResizeHeight 属性と maxResizeWidth/maxResizeHeight 属性の間のサイズ範囲内でサイズ変更可能です。つまり、それらの間の任意のサイズ範囲に適応する必要があります。

たとえば、配置時にウィジェットのデフォルト サイズを設定するには、次の属性を設定します。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

つまり、ウィジェットのデフォルト サイズは、targetCellWidth 属性と targetCellHeight 属性で指定されている 3x2 セル、または Android 11 以前を搭載しているデバイスの場合は minWidth と minHeight で指定されている 180×110 dp です。後者の場合、セル単位のサイズはデバイスによって異なることがあります。

また、ウィジェットのサポートされているサイズ範囲を設定するには、次の属性を設定します。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

上記の属性で指定されているように、ウィジェットの幅は 180 dp から 530 dp まで、高さは 110 dp から 450 dp までサイズ変更できます。次の条件が満たされている限り、ウィジェットは 3x2 から 5x2 セルにサイズ変更できます。

  • デバイスに 5x4 のグリッドがある。
  • セル数と利用可能なサイズ(dp)のマッピングは、このページの最小サイズの推定値を示す表に従います。
  • ウィジェットはそのサイズ範囲に適応します。

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(180f, 110f) to smallView,
    SizeF(270f, 110f) to mediumView,
    SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

ウィジェットが、前のコード スニペットで定義されているレスポンシブ レイアウトを使用しているとします。つまり、R.layout.widget_weather_forecast_small として指定されたレイアウトは、180 dp(minResizeWidth)x 110 dp(minResizeHeight)から 269x279 dp(次のカットオフ ポイント - 1)まで使用されます。同様に、R.layout.widget_weather_forecast_medium は 270x110 dp から 270x279 dp まで、R.layout.widget_weather_forecast_large は 270x280 dp から 530 dp(maxResizeWidth)x 450 dp(maxResizeHeight)まで使用されます。

ユーザーがウィジェットのサイズを変更すると、次の例に示すように、セルの各サイズに合わせて外観が変化します。

最小の 3x2 グリッドサイズの天気ウィジェットの例。UI には、場所の名前(東京)、気温(14°)、部分的に曇りの天気を示す記号が表示されます。
図 2. 3x2 R.layout.widget_weather_forecast_small.

4x2 の「中」サイズの天気ウィジェットの例。この方法でウィジェットのサイズを変更すると、以前のウィジェット サイズのすべての UI がベースとなり、ラベル「Mostly cloudy」と午後 4 時から午後 7 時までの気温の予報が追加されます。
図 3. 4x2 R.layout.widget_weather_forecast_medium.

5x2 の「中」サイズの天気ウィジェットの例。この方法でウィジェットのサイズを変更すると、以前のサイズと同じ UI になりますが、1 セル分の長さだけ引き伸ばされ、水平方向のスペースが大きくなります。
図 4. 5x2 R.layout.widget_weather_forecast_medium.

5x3 の「大」サイズの天気ウィジェットの例。この方法でウィジェットのサイズを変更すると、以前のウィジェット サイズのすべての UI がベースとなり、火曜日と水曜日の天気予報を含むビューがウィジェット内に追加されます。晴れや雨の天気を示す記号と、各日の最高気温と最低気温。
図 5. 5x3 R.layout.widget_weather_forecast_large.

5x4 の「大」サイズの天気ウィジェットの例。このようにウィジェットのサイズを変更すると、以前のウィジェット サイズのすべての UI がベースとなり、木曜日と金曜日(および、対応する天気の種類を示す記号、各日の最高気温と最低気温)が追加されます。
図 6. 5x4 R.layout.widget_weather_forecast_large.