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

Как создать письмо
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 × 110 dp (minResizeHeight) до 250 × 200 dp (maxResizeWidthmaxResizeHeight).

Ваш виджет должен поддерживать размеры от 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
3:1 203x102dp 412x51 дп
4:1 276x102dp 554x51dp
5:1 349x102dp 697 x 51 дп
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атрибута minResizeWidth значение не более 56 dp , чтобы оно было меньше 57 dp (минимальной ширины ячейки в портретном режиме). Аналогично, если вы хотите, чтобы высота виджета могла изменяться в одной ячейке на одном и том же устройстве, вам нужно задать minResizeHeight для атрибута minResizeHeight значение не более 50 dp, чтобы оно было меньше 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 до 530 dp, а высоту – от 110 до 450 dp. Размер виджета можно изменить с 3x2 до 5x2 ячеек, если выполняются следующие условия:

  • На устройстве используется сетка 5 x 4.
  • Соответствие между количеством ячеек и доступным размером в единицах 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) до 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.

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

Пример виджета погоды среднего размера (5x2). При таком изменении размера виджета интерфейс остается прежним, но растягивается на одну ячейку по горизонтали.
Рисунок 4. 5x2 R.layout.widget_weather_forecast_medium.

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

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