Обеспечьте гибкие макеты виджетов

На этой странице описаны усовершенствования размера виджетов и большая гибкость, представленная в 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
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

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

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

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

В следующем примере кода показано, как предоставить список макетов.

Котлин

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

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

Ява

@Override
public void onUpdate(...) {
    RemoteViews smallView = ...;
    RemoteViews tallView = ...;
    RemoteViews wideView = ...;

    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    viewMapping.put(new SizeF(150f, 100f), smallView);
    viewMapping.put(new SizeF(150f, 200f), tallView);
    viewMapping.put(new SizeF(215f, 100f), wideView);
    RemoteViews remoteViews = new RemoteViews(viewMapping);

    appWidgetManager.updateAppWidget(id, remoteViews);
}

Предположим, что виджет имеет следующие атрибуты:

<appwidget-provider
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

Предыдущий фрагмент кода означает следующее:

  • smallView поддерживает от 160dp ( minResizeWidth ) × 110dp ( minResizeHeight ) до 160dp × 199dp (следующая точка отсечки — 1dp).
  • tallView поддерживает разрешение от 160dp × 200dp до 214dp (следующая точка отсечки — 1) × 200dp.
  • wideView поддерживает разрешение от 215dp × 110dp ( minResizeHeight ) до 250dp ( maxResizeWidth ) × 200dp ( 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?.getParcelableArrayList<SizeF>(
            AppWidgetManager.OPTION_APPWIDGET_SIZES
    )
    // 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 { }

Ява

@Override
public void onAppWidgetOptionsChanged(
    Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
        newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the RemoteViews that you want.
    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
        viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

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

Определите размер вашего виджета

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

Домашние экраны Android предлагают пользователям сетку доступных мест, на которых они могут размещать виджеты и значки. Эта сетка может различаться в зависимости от устройства; например, многие телефоны предлагают сетку 5x4, а планшеты могут предлагать сетку большего размера. Когда ваш виджет добавляется, он растягивается, чтобы занять минимальное количество ячеек по горизонтали и вертикали, необходимое для удовлетворения ограничений для его targetCellWidth и targetCellHeight на устройствах под управлением Android 12 или выше, или ограничений minWidth и minHeight на устройствах под управлением Android 11 (API уровень 30) или ниже.

Ширина и высота ячейки, а также размер автоматических полей, применяемых к виджетам, могут различаться на разных устройствах. Используйте следующую таблицу, чтобы примерно оценить минимальные размеры вашего виджета в обычном телефоне с сеткой 5x4, учитывая желаемое количество занятых ячеек сетки:

Количество ячеек (ширина х высота) Доступный размер в портретном режиме (dp) Доступный размер в ландшафтном режиме (dp)
1х1 57x102dp 127 x 51 дп
2х1 130 x 102 дп 269 x 51 дп
3х1 203 x 102 дп 412 x 51 дп
4х1 276 x 102 дп 554 x 51 дп
5х1 349 x 102 дп 697 x 51 дп
5х2 349 x 220 пикселей 697x117dp
5х3 349 x 337 дп 697x184dp
5х4 349 x 455 дп 697 x 250 пикселей
... ... ...
нхм (73н – 16) х (118м – 16) (142н – 15) х (66м – 15)

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

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

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

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

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

<appwidget-provider
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

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

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

<appwidget-provider
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

Как указано предыдущими атрибутами, ширина виджета может быть изменена от 180dp до 530dp, а его высота — от 110dp до 450dp. Размер виджета затем можно изменить с 3x2 до 5x2 ячеек при соблюдении следующих условий:

Котлин

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

Ява

RemoteViews smallView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_small);
RemoteViews mediumView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_medium);
RemoteViews largeView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_large);

Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
viewMapping.put(new SizeF(180f, 110f), smallView);
viewMapping.put(new SizeF(270f, 110f), mediumView);
viewMapping.put(new SizeF(270f, 280f), largeView);
RemoteViews remoteViews = new RemoteViews(viewMapping);

appWidgetManager.updateAppWidget(id, remoteViews);

Предположим, что виджет использует адаптивные макеты, определенные в предыдущих фрагментах кода. Это означает, что макет, указанный как R.layout.widget_weather_forecast_small , используется от 180dp ( minResizeWidth ) x 110dp ( minResizeHeight ) до 269x279dp (следующие точки отсечения — 1). Аналогично, R.layout.widget_weather_forecast_medium используется от 270x110dp до 270x279dp, а R.layout.widget_weather_forecast_large используется от 270x280dp до 530dp ( maxResizeWidth ) x 450dp ( maxResizeHeight ).

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

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

Пример виджета погоды среднего размера 4x2. Изменение размера виджета таким образом основывается на всем пользовательском интерфейсе предыдущего размера виджета и добавляет метку «Преимущественно облачно» и прогноз температуры с 16:00 до 19:00.
Рисунок 3. 4x2 R.layout.widget_weather_forecast_medium .

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

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

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