Гибкие макеты виджетов

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

На этой странице описаны уточнения размеров виджетов и большая гибкость, представленные в 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. Пример адаптивного макета.

Предоставьте точные макеты

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

Чтобы реализовать это решение, в приложении необходимо выполнить следующие действия:

  1. Перегрузите метод AppWidgetProvider.onAppWidgetOptionsChanged(), который вызывается при изменении набора размеров.

  2. Вызовите метод AppWidgetManager.getAppWidgetOptions(), который возвращает объект Bundle с размерами.

  3. Получите доступ к ключу AppWidgetManager.OPTION_APPWIDGET_SIZES из Bundle.

В примере кода ниже показано, как задать точные макеты.

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

Как выбрать размер виджета

Для каждого виджета необходимо задать атрибуты targetCellWidth и targetCellHeight (для устройств с Android 12 или более поздней версией) или minWidth и minHeight (для всех версий Android), указывающие минимальный объем пространства, который он занимает по умолчанию. Однако когда пользователи добавляют виджет на главный экран, он обычно занимает больше места, чем указано в минимальных значениях.

Главные экраны Android предлагают пользователям сетку доступных пространств, на которых они могут размещать виджеты и значки. Размер сетки зависит от устройства. Например, на многих телефонах используется сетка 5 x 4, а на планшетах – сетка большего размера. При добавлении виджет растягивается, чтобы занять минимальное количество ячеек по горизонтали и вертикали, необходимое для соблюдения ограничений targetCellWidth и targetCellHeight на устройствах с Android 12 или более поздней версии или ограничений minWidth и minHeight на устройствах с Android 11 (уровень API 30) или более ранней версии.

Размеры ячеек и автоматические поля различаются на разных устройствах. В таблице ниже приведены минимальные размеры виджета на типичном телефоне с сеткой 5 x 4 в зависимости от количества занимаемых ячеек.

Количество ячеек (ширина x высота) Доступный размер в вертикальном режиме (dp) Доступный размер в горизонтальном режиме (dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349 x 102 dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
… … …
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Размеры ячеек в вертикальном режиме определяют значения атрибутов minWidth, minResizeWidth и maxResizeWidth. Аналогично, размеры ячеек в горизонтальном режиме определяют значения атрибутов minHeight, minResizeHeight и maxResizeHeight.

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

Например, если вы хотите, чтобы ширину виджета можно было уменьшить до одной ячейки на Google Pixel 4, вам нужно установить для minResizeWidth значение не более 56 dp, чтобы значение атрибута minResizeWidth было меньше 57 dp, поскольку ширина ячейки в портретном режиме составляет не менее 57 dp. Аналогично, если вы хотите, чтобы высота виджета изменялась в пределах одной ячейки на том же устройстве, вам необходимо установить для minResizeHeight значение не более 50 dp, чтобы значение атрибута minResizeHeight было меньше 51 dp, поскольку высота одной ячейки в горизонтальном режиме составляет не менее 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>

Это означает, что размер виджета по умолчанию составляет 3 x 2 ячейки, как указано в атрибутах targetCellWidth и targetCellHeight, или 180 x 110 dp, как указано в атрибутах minWidth и minHeight для устройств с Android 11 или более ранней версии. В последнем случае размер в ячейках может варьироваться в зависимости от устройства.

Чтобы задать поддерживаемые диапазоны размеров виджета, можно использовать следующие атрибуты:

<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. Размер виджета можно изменить с 3 x 2 до 5 x 2 ячеек, если выполняются следующие условия:

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) до 269 x 279 dp (следующие пороговые значения минус 1). Аналогично, R.layout.widget_weather_forecast_medium используется для размеров от 270 x 110 dp до 270 x 279 dp, а R.layout.widget_weather_forecast_large – для размеров от 270 x 280 dp до 530 dp (maxResizeWidth) x 450 dp (maxResizeHeight).

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

Пример виджета погоды в наименьшем размере сетки 3 x 2. В интерфейсе показано название местоположения (Токио), температура (14 °C) и символ, обозначающий переменную облачность.
Рисунок 2. 3x2 R.layout.widget_weather_forecast_small.

Пример виджета погоды среднего размера 4 x 2.             При таком изменении размера виджета
            сохраняются все элементы интерфейса из предыдущего размера виджета,
            а также добавляется метка &quot;Облачно&quot; и прогноз температуры с
            16:00 до 19:00.
Рисунок 3. 4x2 R.layout.widget_weather_forecast_medium.

Пример виджета погоды среднего размера (5 x 2). В этом случае виджет будет выглядеть так же, как и при предыдущем размере, но растянется на одну ячейку, чтобы занять больше места по горизонтали.
Рисунок 4. 5x2 R.layout.widget_weather_forecast_medium.

Пример виджета погоды большого размера (5 x 3). При таком изменении размера виджета сохраняются все элементы интерфейса из предыдущих размеров и добавляется представление с прогнозом погоды на вторник и среду. Значки, указывающие на солнечную или дождливую погоду, а также максимальная и минимальная температура для каждого дня.
Рисунок 5. 5x3 R.layout.widget_weather_forecast_large.

Пример виджета погоды большого размера (5 x 4). При таком изменении размера виджета в него добавляются все элементы интерфейса из предыдущих размеров, а также четверг и пятница (и соответствующие им символы, указывающие на тип погоды, а также высокую и низкую температуру для каждого дня).
Рисунок 6. 5x4 R.layout.widget_weather_forecast_large.