Как показывать контент на весь экран в представлениях

Как создать письмо
Jetpack Compose – это рекомендуемый набор инструментов для создания интерфейсов Android. Узнайте, как работать с режимом "от края до края" в Compose.

Если вы используете SDK версии 35 или более поздней на устройстве с Android 15 или более поздней версии, приложение будет отображаться от края до края. Окно занимает всю ширину и высоту экрана, отрисовываясь за системными строками. К системным строкам относятся строка состояния, строка заголовка и панель навигации.

Во многих приложениях есть верхняя панель. Верхняя панель приложения должна занимать всю ширину экрана и располагаться под строкой состояния. При прокрутке контента верхняя панель приложения может уменьшаться до высоты строки состояния.

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

Рисунок 1. Системные строки в макете от края до края.

При реализации макета с краями экрана в приложении учитывайте следующее:

  1. Как включить отображение от края до края
  2. Используйте адаптивные макеты, чтобы оптимизировать работу с приложением на устройствах разных форм-факторов
  3. Как устранять визуальные наложения
  4. Рекомендуем показывать затемнение за системными строками
Пример изображения за строкой состояния
Рисунок 2. Пример изображения за строкой состояния.

Включить отображение от края до края

Если ваше приложение поддерживает SDK 35 или более поздние версии, режим "от края до края" автоматически включается на устройствах с Android 15 и более поздними версиями ОС.

Чтобы включить отображение на весь экран в предыдущих версиях Android, вручную вызовите enableEdgeToEdge в onCreate вашего Activity.

Kotlin

 override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         WindowCompat.enableEdgeToEdge(window)
        ...
      }

Java

 @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowCompat.enableEdgeToEdge(getWindow());
        ...
      }

По умолчанию enableEdgeToEdge() делает системные строки прозрачными, за исключением режима навигации с помощью трех кнопок, в котором панель навигации становится полупрозрачной. Цвета системных значков и затемнения экрана меняются в зависимости от того, какая тема выбрана в системе: светлая или тёмная.

Чтобы включить отображение от края до края в приложении без использования функции enableEdgeToEdge(), ознакомьтесь с разделом Как вручную настроить отображение от края до края.

Как обрабатывать перекрытия с помощью отступов

Некоторые элементы интерфейса приложения могут отрисовываться за системными строками, как показано на рисунке 3.

Чтобы избежать наложения, можно использовать вставки, которые указывают, какие части экрана пересекаются с системным интерфейсом, например панелью навигации или строкой состояния. Пересечение может означать показ поверх контента, но также может сообщать приложению о системных жестах.

Для отображения приложения на весь экран используются следующие типы отступов:

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

  • Вставки для вырезов на экране – для областей, где из-за формы устройства может быть вырез на экране.

  • Вставки для системных жестов – области навигации с помощью жестов, используемые системой и имеющие приоритет над вашим приложением.

Вставки системных панелей

Вставки системной панели – самый распространенный тип вставок. Они представляют собой область, в которой системный интерфейс отображается по оси Z над вашим приложением. Их лучше всего использовать для перемещения или заполнения представлений в приложении, которые можно нажать и которые не должны быть визуально скрыты системными панелями.

Например, плавающая кнопка действия на рисунке 3 частично закрыта панелью навигации:

Пример реализации режима "от края до края", в котором панель навигации закрывает плавающую кнопку действия
Рисунок 3. Панель навигации перекрывает плавающую кнопку действия в макете с краями от края до края.

Чтобы избежать визуального перекрытия в режиме управления жестами или кнопками, можно увеличить поля представления с помощью getInsets(int) с WindowInsetsCompat.Type.systemBars().

В примере кода ниже показано, как реализовать отступы системной строки:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Apply the insets as a margin to the view. This solution sets
  // only the bottom, left, and right dimensions, but you can apply whichever
  // insets are appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  v.updateLayoutParams<MarginLayoutParams> {
      leftMargin = insets.left
      bottomMargin = insets.bottom
      rightMargin = insets.right
  }

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(fab, (v, windowInsets) -> {
  Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  // Apply the insets as a margin to the view. This solution sets only the
  // bottom, left, and right dimensions, but you can apply whichever insets are
  // appropriate to your layout. You can also update the view padding if that's
  // more appropriate.
  MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
  mlp.leftMargin = insets.left;
  mlp.bottomMargin = insets.bottom;
  mlp.rightMargin = insets.right;
  v.setLayoutParams(mlp);

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Если применить это решение к примеру, показанному на рисунке 3, то в режиме кнопки визуального перекрытия не будет (рисунок 4).

полупрозрачная панель навигации, не закрывающая кнопку;
Рисунок 4. Устранение визуального перекрытия в режиме кнопки.

То же самое относится к режиму навигации жестами, как показано на рисунке 5:

отображение от края до края с навигацией жестами;
Рисунок 5. Устранение визуального перекрытия в режиме навигации с помощью жестов.

Вставки выреза на экране

На некоторых устройствах есть вырезы на экране. Обычно вырез находится в верхней части экрана и входит в строку состояния. Если экран устройства находится в горизонтальном режиме, вырез может быть расположен на вертикальном краю. В зависимости от контента, который приложение показывает на экране, вам следует реализовать отступы, чтобы избежать вырезов. По умолчанию приложения рисуют в области выреза.

Например, на многих экранах приложений показывается список объектов. Не закрывайте элементы списка вырезом на экране или системными панелями.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, insets ->
  val bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
      or WindowInsetsCompat.Type.displayCutout()
  )
  v.updatePadding(
    left = bars.left,
    top = bars.top,
    right = bars.right,
    bottom = bars.bottom,
  )
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(mBinding.recyclerView, (v, insets) -> {
  Insets bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
    | WindowInsetsCompat.Type.displayCutout()
  );
  v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
  return WindowInsetsCompat.CONSUMED;
});

Определите значение WindowInsetsCompat, выполнив логическую операцию ИЛИ для системных панелей и типов вырезов на экране.

Задайте для свойства clipToPadding значение RecyclerView, чтобы отступ прокручивался вместе с элементами списка. Это позволяет элементам уходить за системные панели при прокрутке, как показано в следующем примере.

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

Вставки для системных жестов

Вставки системных жестов – это области окна, в которых системные жесты имеют приоритет над действиями в приложении. На рисунке 6 эти области показаны оранжевым цветом.

Пример системных жестов
Рисунок 6. Вставки для системных жестов.

Как и в случае с системными строками, чтобы избежать наложения на вставки для системных жестов, можно использовать getInsets(int) с WindowInsetsCompat.Type.systemGestures().

Используйте эти вставки, чтобы перемещать или дополнять представления, которые можно пролистывать, вдали от краев. Распространенные варианты использования: всплывающие снизу экраны, пролистывание в играх и карусели, реализованные с помощью ViewPager2.

В Android 10 и более поздних версиях отступы для системных жестов включают нижний отступ для жеста "Домой" и левый и правый отступы для жестов "Назад":

Пример измерений отступа для системных жестов
Рисунок 7. Размеры отступов для системных жестов.

В примере кода ниже показано, как реализовать вставки для системных жестов:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Компоненты материалов

Многие компоненты Android Material (com.google.android.material), основанные на представлениях, автоматически обрабатывают вставки, в том числе BottomAppBar, BottomNavigationView, NavigationRailView и NavigationView.

Однако AppBarLayout не обрабатывает вставки автоматически. Добавьте android:fitsSystemWindows="true" для обработки верхних отступов.

Узнайте, как работать с отступами с помощью компонентов Material в Compose.

Обратная совместимость при отправке врезок

Чтобы запретить отправку отступов в дочерние представления и избежать чрезмерного заполнения, вы можете использовать константу WindowInsetsCompat.CONSUMED. Однако на устройствах с Android 10 (уровень API 29 и ниже) после вызова WindowInsetsCompat.CONSUMED отступы не передаются соседним элементам, что может привести к нежелательному визуальному перекрытию.

Пример неправильной отправки встроенного объявления
Рисунок 8. Пример неправильной отправки вставки. Отступы не передаются соседним представлениям после того, как ViewGroup 1 использует отступы на устройствах с Android 10 (уровень API 29) и более ранних версий. В результате TextView 2 перекрывает системную панель навигации. Однако в Android 11 (уровень API 30) и более поздних версиях отступы передаются соседним представлениям, как и ожидалось.

Чтобы убедиться, что вставки отправляются дочерним элементам во всех поддерживаемых версиях Android, используйте ViewGroupCompat#installCompatInsetsDispatch перед использованием вставок. Этот метод доступен в AndroidX Core и Core-ktx 1.16.0-alpha01 и более поздних версиях.

Kotlin

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
val rootView = findViewById(R.id.main)
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView)

Java

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
LinearLayout rootView = findViewById(R.id.main);
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView);
Пример отправки с фиксированным отступом
Рисунок 9. Исправлена отправка вставки после вызова ViewGroupCompat#installCompatInsetsDispatch.

Режим комфортного просмотра

Некоторый контент лучше воспринимается в полноэкранном режиме, поскольку это позволяет пользователю полностью погрузиться в происходящее на экране. Вы можете скрыть системные панели в режиме погружения, используя библиотеки WindowInsetsController и WindowInsetsControllerCompat:

Kotlin

val windowInsetsController =
      WindowCompat.getInsetsController(window, window.decorView)

// Hide the system bars.
windowInsetsController.hide(Type.systemBars())

// Show the system bars.
windowInsetsController.show(Type.systemBars())

Java

Window window = getWindow();
WindowInsetsControllerCompat windowInsetsController =
      WindowCompat.getInsetsController(window, window.getDecorView());
if (windowInsetsController == null) {
    return;
  }
// Hide the system bars.
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());

// Show the system bars.
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());

Подробнее о том, как скрыть системные панели в режиме погружения…

Значки системной панели

Вызов enableEdgeToEdge гарантирует, что цвета значков системной строки будут обновляться при смене темы устройства.

При показе от края до края вам может понадобиться вручную изменить цвета значков системной строки, чтобы они контрастировали с фоном приложения. Например, чтобы создать светлые значки строки состояния:

Kotlin

WindowCompat.getInsetsController(window, window.decorView)
    .isAppearanceLightStatusBars = false

Java

WindowCompat.getInsetsController(window, window.getDecorView())
    .setAppearanceLightStatusBars(false);

Защита системной панели

Если ваше приложение поддерживает SDK 35 или более поздние версии, контент будет показываться на весь экран. Системная строка состояния и панель навигации жестами прозрачны, а панель навигации с тремя кнопками – полупрозрачна. Вызовите enableEdgeToEdge, чтобы обеспечить обратную совместимость.

Однако системные настройки по умолчанию могут не подходить для всех случаев использования. Чтобы решить, какие системные строки использовать – прозрачные или полупрозрачные, ознакомьтесь с рекомендациями по дизайну системных строк Android и рекомендациями по дизайну от края до края.

Как создать прозрачные системные строки

Чтобы создать прозрачную строку состояния, задайте целевую версию Android 15 (SDK 35) или более позднюю либо вызовите enableEdgeToEdge() с аргументами по умолчанию для более ранних версий.

Чтобы создать прозрачную панель навигации жестами, выберите в качестве целевой ОС Android 15 или более позднюю версию или вызовите функцию enableEdgeToEdge() с аргументами по умолчанию для более ранних версий. Если вы используете панель навигации с тремя кнопками, задайте для параметра Window.setNavigationBarContrastEnforced значение false. В противном случае будет применен полупрозрачный экран.

Как создать полупрозрачные системные панели

Чтобы создать полупрозрачную строку статуса, выполните следующие действия:

  1. Обновите зависимость androidx-core до версии 1.16.0-beta01 или более поздней.
  2. Оберните макет XML в теги androidx.core.view.insets.ProtectionLayout и назначьте идентификатор.
  3. Получите программный доступ к ProtectionLayout, чтобы настроить защиту, указав сторону и GradientProtection для строки состояния.

<androidx.core.view.insets.ProtectionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_protection"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:id="@+id/item_list"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--items-->

    </ScrollView>

</androidx.core.view.insets.ProtectionLayout>

findViewById<ProtectionLayout>(R.id.list_protection)
    .setProtections(
        listOf(
            GradientProtection(
                WindowInsetsCompat.Side.TOP,
                // Ideally, this is the pane's background color
                paneBackgroundColor
            )
        )
    )

Убедитесь, что значение ColorInt, переданное в GradientProtection, соответствует фону контента. Например, в макете "список и подробные сведения", который показывается на складном устройстве, могут быть разные GradientProtections разных цветов для панели списка и панели подробных сведений.

Рисунок 1. Градиентное средство защиты разных цветов.

Не создавайте полупрозрачную панель навигации жестами. Чтобы создать полупрозрачную панель навигации с тремя кнопками, выполните одно из следующих действий:

  • Если макет уже заключен в тег ProtectionView, вы можете передать дополнительный тег ColorProtection или GradientProtection в метод setProtections. Перед этим убедитесь, что window.isNavigationBarContrastEnforced = false.
  • В противном случае задайте значение window.isNavigationBarContrastEnforced = true.

Другие советы

Дополнительные советы по работе с вставками.

Как сделать прокручиваемый контент от края до края

Убедитесь, что последний элемент списка не закрыт системными панелями в RecyclerView или NestedScrollView, обработав вставки и задав для clipToPadding значение false.

На видео ниже показано устройство RecyclerView с отключенным (слева) и включенным (справа) полноэкранным режимом.

Пример кода можно найти в разделе Создание динамических списков с помощью RecyclerView.

Как сделать полноэкранные диалоговые окна от края до края

Чтобы диалоговое окно занимало весь экран, вызовите enableEdgeToEdge для объекта Dialog.

Kotlin

class MyAlertDialogFragment : DialogFragment() {
    override fun onStart(){
        super.onStart()
        dialog?.window?.let { WindowCompat.enableEdgeToEdge(it) }
    }
    ...
}

Java

public class MyAlertDialogFragment extends DialogFragment {
    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            Window window = dialog.getWindow();
            if (window != null) {
                WindowCompat.enableEdgeToEdge(window);
            }
        }
    }
    ...
}

Дополнительные ресурсы

Дополнительную информацию о том, как сделать так, чтобы изображение занимало весь экран, можно найти в следующих источниках:

Блоги

Проектирование

Другая документация

Видео