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

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

Размер каждого виджета можно изменять в пределах диапазонов между атрибутами 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>

Как указано в предыдущих атрибутах, ширина виджета может изменяться от 180 до 530 dp, а его высота — от 110 до 450 dp. Размер виджета может изменяться от 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 .

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

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