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

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

Количество ячеек (ширина 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
... ... ...
нксм (73н - 16) х (118м - 16) (142н - 15) х (66м - 15)

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

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

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