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

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

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

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

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

Рисунок 1. Системные балки в поперечном расположении.

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

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

Включить дисплей от края до края

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

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

Котлин

 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.

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

К типам отступов, применяемым при отображении приложения от края до края, относятся:

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

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

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

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

Отступы системной панели — наиболее часто используемый тип отступов. Они представляют собой область, где отображается системный пользовательский интерфейс по оси 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
}

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:

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

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

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

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

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

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

Котлин

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

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

Котлин

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 Components (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 и выше.

Котлин

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

Котлин

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

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

Котлин

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. Для настройки защиты, указав сторону и параметр GradientProtection для строки состояния, используйте программный доступ ProtectionLayout

<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 для этого диалогового окна.

Котлин

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

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

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

Блоги

Дизайн

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

Видео