Отображение контента от края до края в представлениях

Попробуйте способ «Композиции»
Jetpack Compose — рекомендуемый набор инструментов для разработки пользовательского интерфейса для Android. Узнайте, как работать с интерфейсом «от края до края» в Compose.

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

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

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

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

При реализации макета «от края до края» в вашем приложении помните следующее:

  1. Включить дисплей от края до края
  2. Устраните любые визуальные наложения.
  3. Рассмотрите возможность показа сеток за системными решетками.
пример изображения за строкой состояния
Рисунок 2. Пример изображения за строкой состояния.

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

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

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

Котлин

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

Ява

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

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

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

Обработка перекрытий с помощью вставок

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

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

Типы вставок, которые применяются для отображения вашего приложения от края до края:

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

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

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

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

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

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

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

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

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

Котлин

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
}

Ява

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:

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

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

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

Вставки для вырезов на дисплее

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

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

Котлин

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
}

Ява

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. Измерения вставки жестов системы.

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

Котлин

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
}

Ява

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" для обработки верхних вставок.

Узнайте, как обрабатывать вставки с компонентами материалов в 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 и выше.

Котлин

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

Ява

// 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 :

Котлин

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

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

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

Ява

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 обеспечивает обновление цветов значков системной панели при изменении темы устройства.

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

Котлин

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

Ява

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 . Если ваше приложение вызывает enableEdgeToEdge, window.isNavigationBarContrastEnforced = true .

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

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

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

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

На следующем видео показан RecyclerView с отключенным (слева) и включенным (справа) дисплеем от края до края:

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

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

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

Котлин

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

Ява

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

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

Дополнительную информацию о переходе от края к краю см. в следующих ссылках.

Блоги

Дизайн

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

Видео