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

Попробуйте способ создания композиций.
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 adds APIs letting you ensure your widget is sized more reliably across different devices with varying screen sizes.

В дополнение к существующим атрибутам 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)
}

Java

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

Java

@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
2х1 130x102dp 269x51dp
3х1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
... ... ...
нхм (73n - 16) x (118m - 16) (142n - 15) x (66m - 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 ячеек при соблюдении следующих условий:

  • Устройство имеет сетку 5x4.
  • The mapping between the number of cells and the available size in dps follows the table showing estimation of minimum dimensions in this page.
  • Виджет адаптируется к этому диапазону размеров.

Котлин

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

Java

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 .

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

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