Fitssystemwindows android что это
Перейти к содержимому

Fitssystemwindows android что это

  • автор:

WindowInsets и fitsSystemWindows

Любая активность размещается в специальном контейнере и пользователь видит её вместе с системными элементами — строкой состояния, панелью навигации. Всё вместе это контролируется классом Window. Но иногда требуется изменить стандартное поведение. Самый простой пример — игры в полноэкранном режиме, когда мы хотим скрыть все лишние элементы с экрана. Другой вариант — полноэкранный режим, но системные элементы накладываются поверх игры. Ещё один вариант — динамическое изменение, когда пользователь начинает прокручивать список вверх, то системная строка состояния исчезает, а при прокрутке вниз снова появляется.

Любая активность может получить доступ к окну Window через метод Activity#getWindow().

 Window window = this.getWindow(); System.out.println(window.getClass().getSimpleName()); // PhoneWindow 

Аналогично класс Dialog имеет своё окно Dialog#getWindow().

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

Поведение Window в разных версиях Android менялась. До KitKat была упрощённая версия не учитывала системные элементы управления. Доступ к ним осуществлялся через метод setSystemUiVisibility(). Также было множество флагов настроек: SYSTEM_UI_FLAG_VISIBLE, SYSTEM_UI_FLAG_FULLSCREEN и другие.

Начиная с KitKat, Android стала поддерживать полупрозрачные системные панели. Появились атрибуты androd:windowTranslucentStatus, android:windowsTranslucentNavigation.

В Lollipop происходит дальнейшая интеграция, системные элементы входят в состав Window и появилась возможность управлять фоном через android:windowDrawsSystemBarBackgrounds.

Мы можем узнать цвета системных элементов.

 ImageView image = findViewById(R.id.imageView); Window window = this.getWindow(); int navigationColor = window.getNavigationBarColor(); int statusBarColor = window.getStatusBarColor(); image.setBackgroundColor(statusBarColor); 

android:fitsSystemWindows=»true»

Данный флаг использовался для этой же цели. Сейчас в документации указано, что флаг считается устаревшим с версии API 20.

Данный флаг не будет иметь значения для многих видов макетов типа LinearLayout, FrameLayout. Но есть несколько видов макетов, к которым флаг применить можно. К таким макетам относятся DrawerLayout, CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout, NavigationView.

Лучше вызывать метод onApplyWindowInsets().

Что делать не надо — вручную прописывать размеры строки состояния в ресурсах. Каждое устройство и каждая версия могут иметь свои размеры системных компонентов и ваша попытка видоизменить размеры приведёт к некрасивым последствиям в виде наслоений разных частей компонентов.

Для решения проблемы используйте методы getSystemWindowInsetLeft(), getSystemWindowInsetTop(), getSystemWindowInsetRight(), getSystemWindowInsetBottom() класса WindowInsets/WindowInsetsCompat.

При разработке собственного компонента можете использовать код:

 // Kotlin myView.setOnApplyWindowInsetListener // Ваш код для обработки отступов . val statusBarSize = insets.systemWindowInsetTop return insets.consumeSystemWindowInsets() > 

Как уже упоминалось выше, отступы не работают с некоторыми видами макетов. Вы можете переопределить поведение макета, наследуясь от основного макета и переопределяя метод.

 // Kotlin class CustomLayout : LinearLayout < override fun onApplyWindowInsets( insets: WindowInsets): WindowInsets < // Ваш код для обработки отступов . return insets.consumeSystemWindowInsets() >> 

BottomNavigationView

Как работать с нижней частью экрана рассказано в статье WindowInsets — Listeners to layouts (12 Apr 2019).

Шаблон «Basic Views Activity»

Раньше шаблон назывался «Basic Activity» и использовал компоненты из библиотеки Android Support Design и был специально спроектирован под новый дизайн Material Design. Позже появился Jetpack AndroidX и шаблон переделали. Затем его в очередной раз изменили, добавив фрагменты и новый способ навигации между ними.

Создадим проект при помощи шаблона Basic Views Activity.

Заполняем нужные поля.

Basic Views Activity

При использовании шаблона будут задействованы различные компоненты: CoordinatorLayout, AppBarLayout, FloatingActionButton, Snackbar.

Первые три компонента доступны через XML-разметку, а Snackbar вызывается программно.

Изучим разметку активности activity_main.xml.

Корневым элементом разметки служит компонент CoordinatorLayout, который является наследником стандартного FrameLayout. Но если обычный FrameLayout позволяет просто накладывать один компонент поверх другого, то CoordinatorLayout позволяет координировать определённые зависимости между дочерними компонентами. Сейчас мы не будем вдаваться в тонкости, этой теме можно посвятить отдельную статью.

Далее идёт AppBarLayout с вложенным MaterialToolbar. Связка компонентов образуют внешний вид и поведение продвинутого заголовка экрана активности, который пришёл на смену ActionBar из Android 4.x, который в свою очередь заменил стандартный заголовок (Title) в Android 2.x. Опять оставляем их пока без внимания.

Потом следует интересный приём, который вам может впоследствии пригодиться. В существующую разметку вставляется ещё одна разметка при помощи тега include и с указанием вставляемой разметки — layout/content_main.xml.

Завершает основную разметку красивая кнопка FloatingActionButton, которая на самом деле является продвинутым вариантом ImageView. Но в спецификации Material Design кнопке отводится большая роль и практически является визитной карточкой нового дизайна. Наверняка, вы уже видели её на различных картинках. Раньше кнопка была круглой, но в Material 3 она стала квадратной с закруглёнными углами.

Подробнее о возможностях кнопки в отдельной статье. Обратите внимание, что кнопка «пришпилена» к нижнему правому углу экрана при помощи layout_gravity. В качестве значка используется изображение из системных ресурсов @android:drawable/ic_dialog_email в атрибуте app:srcCompat. Вы можете установить собственное изображение, подходящее по контексту. На данный момент Гугл рекомендует активно использовать векторные изображения.

Заменим значок электронной почты на изображение лапы кота. Щёлкаем правой кнопкой мыши по папке app и вызываем контекстное меню New | Vector Asset. В диалоговом окне щёлкаем по значку Clip Art, чтобы открыть другое окно для выбора значка. В строке поиска набираем pets и находим нужный значок. Выделяем его и нажимаем OK.

Pets Icon

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

Далее нажимаем кнопку Next и в следующем окне запоминаем название файла в ресурсах. Если вы всё-таки поменяли цвет, то логичнее будет поменять и название файла, убрав слово «black». Для этого можно вернуться на шаг назад и отредактировать поле Name. Оставляю на ваше усмотрение.

Pets Icon

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

Pets Icon

Также у активности есть меню в виде трёх точек, как создавать меню вы должны уже знать.

Snackbar

Если раньше для всплывающих сообщений использовались Toast, то теперь можно использовать новый компонент Snackbar.

Класс Snackbar имеет много общего с классом Toast и имеет практически тот же синтаксис.

Пример запуска сообщения находится в файле класса MainActivity.kt.

 binding.fab.setOnClickListener < view ->Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show() > 

Как видите, код очень похож. Но есть и различия. Если Toast является частью активности и выводится поверх неё в нижней части по умолчанию, если не заданы другие параметры, то Snackbar выводится в «подвале» родительского элемента. В первом параметре указывается подходящий компонент, по которому система попытается найти родителя, обычно им является CoordinatorLayout. В некоторых примерах я видел код, когда родитель указывается явно.

Навигация между фрагментами

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

Для начала просто откройте файлы fragment_first.xml и fragment_second.xml в папке res/layout. Разметка у них идентичная — кнопка и текстовая метка. Измените текст, если хотите.

Общение между фрагментами основано на новом способе Jetpack Navigation. Мы уже разбирали этот способ при знакомстве с фрагментами. Тогда мы вручную создавали фрагменты, устанавливали связи между ними через граф навигации и т.д. Теперь навигация в готовом виде. Откройте файл res/navigation/nav_graph.xml. Его можно посмотреть в текстовом и визуальном виде. Переключитесь в режим Design, чтобы увидеть связь между фрагментами. Теперь вам всё должно быть знакомо и понятно.

Единственное, что может сбить с толку — наличие binding. Технология ViewBinding появилась несколько лет назад, но я до сих пор не использую её. Вкратце, ViewBinding позволяет обойтись без вызовов методов findViewById().

Второй момент, на который стоит обратить внимание — код для навигации между фрагментами происходит в методе onViewCreated() вместо onCreateView(). Это более лучший вариант, хотя старый вариант вполне рабочий в большинстве случаев.

Подводим итоги — если вам нужен проект с навигацией между фрагментами, кнопкой Fab и вызов Snackbar, то используйте шаблон Basic Views Activity.

Возможно ли программно узнать значение параметра FitsSystemWindows?

Контент залезает под прозрачный статус бар. Есть ли способ программно знать какое было состояние до того, как я сделал его false?

Отслеживать

задан 29 июл 2022 в 10:05

user510583 user510583

можно узнать размеры любого View через соотв. методы после вызова onWindowsFocusChanged

29 июл 2022 в 12:13

Больше контекста. Работаю над SDK которая представляет собой фрагмент. Мне необходимо раскрыть его на весь экран. Использую val window = activity?.window ?: return и WindowCompat.setDecorFitsSystemWindows(window, false). Т.е мне нужно узнать какое значение для fitSystemWindow используется у активити мастер приложения .

Edge-to-edge в Android: делаем правильно

Прошедший Google I/O 2019 принёс массу нашумевших новинок, многие из которых будут влиять на индустрию мобильной разработки в ближайшие годы. Не менее интересно было следить за зарождающимися трендами. Сначала в историю ушли механические клавиши управления, экраны смартфонов становились всё больше, а боковые рамки всё незаметнее. На смену экранным системным кнопкам пришли жесты, оставляя всё больше пространства для потребления контента. Мы на пороге эры Edge-to-Edge.

Что такое Edge-to-Edge? Если понимать буквально, это означает, что ваше приложение должно отображаться на всей видимой поверхности дисплея, от нижней до верхней рамки, не стесняя себя статус-баром и нижними кнопками навигации.

Edge-to-edge на примере системной оболочки Android.

Когда речь идёт про Android, простая идея далеко не всегда проста в реализации. В этой статье речь пойдет о том, как по-максимуму использовать всё доступное место на экране любого девайса, независимо от производителя, версии системы и многообразия настроек, которыми так любят радовать пользователей производители устройств из поднебесной (и не только). Код, представленный в статье, был протестирован на более чем 30-ти девайсах нами собственноручно и на 231 разных устройствах 100 тысячами пользователей наших приложений.

Сама по себе проблема создания edge-to-edge интерфейса не нова и была актуальна задолго до I/O 2019. Наверняка каждый из вас вспомнит, как впервые гуглил что-то из разряда: «android transparent status bar» или «android status bar gradient».

Главными критериями соответствия приложения званию «edge-to-edge» являются наличие:

  • прозрачного Status Bar;
  • прозрачного Navigation Bar.

Подробнее про них на material.io .

Приложение Deezer не сильно переживает о соблюдении принципов Edge-to-Edge

Важно отметить, что речь не идёт о том, чтобы убрать их совсем, как в » fullscreen mode «. Мы оставляем пользователю возможность видеть важную системную информацию и пользоваться привычной навигацией.

Не менее важное требование к решению — масштабируемость и расширяемость. Есть и ряд других:

  • Корректно сдвигать экран над клавиатурой, не сломав поддержку adjustResize-флагов у Activity.
  • Избегать наложения Status Bar и Navigation Bar на UI-элементы приложения, отображая при этом под ними соответствующий фон.
  • Работать на всех девайсах с актуальными версиями Android и выглядеть идентично.

Немного теории

На поиск решения для такой простой, казалось бы, задачи у вас может уйти неожиданно много времени, объяснить которое проектному менеджеру будет непросто. А когда QA всё же найдут злосчастный смартфон, на котором ваш экран выглядит не «по канонам»…

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

В первую очередь, необходимо разобраться с тем, как Android рисует системные панели. Начиная с Android 5.0 было предоставлено удобное API для работы с системными отступами вдоль горизонтальных граней экрана. Они называются WindowInsets и на картинке снизу они окрашены красным:

Также, разработчиками из команды Android были добавлены слушатели, позволяющие подписываться на изменения этих отступов, например, при появлении клавиатуры. Строго говоря, WindowInsets — это отступы вашего layout-файла от границ экрана. При изменении размеров вашей Activity (split-screen mode, появление клавиатуры) будут меняться и Inset’ы. Таким образом, для поддержки edge-to-edge нам нужно сделать так, чтобы этих отступов не было. Экран с нулевыми WindowInsets будет выглядеть так:

Реализация

В нашей реализации мы будем активно оперировать Window и его флагами.

Все примеры будут написаны на Kotlin, но вы без труда сможете реализовать их и на Java, используя вместо extension-функций утилиты.

Первым делом у корневого элемента верстки необходимо явно установить флаг:

android:fitsSystemWindows="true"

Это необходимо делать для того, чтобы корневой View рисовался под системными элементами, а также для корректных измерений Inset’ов при подписке на их изменение.

Теперь переходим к самому главному — убираем границы экрана! Однако, это необходимо делать очень аккуратно. И вот почему:

    Обнулив нижний Inset, мы рискуем остаться без реакции окна на появление клавиатуры: на StackOverflow есть десятки советов по обнулению верхнего Inset’а, но про нижний деликатно молчат. Из-за этого NavigationBar не выходит сделать полностью прозрачным. При обнулении нижнего Inset’а флаг adjustResize перестаёт работать.

Такое поведение допускать нельзя (Toolbar залезает на Status Bar).

Функция removeSystemInsets() выглядит следующим образом:

fun removeSystemInsets(view: View, listener: OnSystemInsetsChangedListener) < ViewCompat.setOnApplyWindowInsetsListener(view) < _, insets ->val desiredBottomInset = calculateDesiredBottomInset( view, insets.systemWindowInsetTop, insets.systemWindowInsetBottom, listener ) ViewCompat.onApplyWindowInsets( view, insets.replaceSystemWindowInsets(0, 0, 0, desiredBottomInset) ) > >

Функция calculateDesiredBottomInset() высчитывает нижний Inset с учётом клавиатуры или без неё, в зависимости от текущей конфигурации устройства.

fun calculateDesiredBottomInset( view: View, topInset: Int, bottomInset: Int, listener: OnSystemInsetsChangedListener ): Int

Для проверки высоты клавиатуры используется метод isKeyboardAppeared(). Мы доверились гипотезе, что клавиатура не может занимать меньше четверти высоты экрана. При желании, ��ы можете как угодно модифицировать логику проверки.

private fun View.isKeyboardAppeared(bottomInset: Int) = bottomInset / resourdisplayMetrics.heightPixels.toDouble() > .25

В методе removeSystemInsets() используется listener. На самом деле, это всего лишь typealias для лямбда-выражения. Его полный код:

typealias OnSystemBarsSizeChangedListener = (statusBarSize: Int, navigationBarSize: Int) -> Unit

Следующим шагом является задание прозрачности системным барам:

window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT

Скомпоновав все вышеописанное, получаем следующий метод:

fun Activity.setWindowTransparency( listener: OnSystemInsetsChangedListener = < _, _ ->> )

Теперь для включения «edge-to-edge» режима у желаемой Activity нужно всего лишь вызвать следующую функцию в методе onCreate():

setWindowTransparency < statusBarSize, navigationBarSize ->//Проставление отступов >

Таким образом, менее чем за 30 строк кода мы достигли «edge-to-edge» эффекта, при этом не нарушая никаких UX-принципов и не лишая пользователя привычных системных элементов управления. Такая реализация может показаться кому-то простой и тривиальной, однако же именно она обеспечивает надёжную работу вашего приложения на любых устройствах.

Добиться «edge-to-edge» эффекта можно ещё примерно сотней разных способов (количество подобных советов на StackOverflow – яркое тому подтверждение), но многие из них ведут либо к некорректному поведению на различных версиях Android, либо не учитывают такие параметры, как необходимость отображения длинных списков, либо ломают ресайз экрана при показе клавиатуры.

Ложка дегтя

Решение, описанное в этой статье, подходит для всех актуальных девайсов. Под актуальными подразумеваются устройства на Android Lollipop (5.0) и выше. Для них решение выше будет работать идеально. А вот для более старых версий Android понадобится своя реализация, так как про WindowInsets в те времена ещё ничего не было известно.

Хорошая новость заключается в том, что на Android KitKat (4.4) прозрачность системных панелей всё же поддерживается. А вот более старые версии такую красоту не поддерживают вовсе, можно даже не пытаться.

Сконцентрируемся на сдвиге Inset’ов в Android 4.4. Это возможно сделать в методе fitSystemWindows(). Таким образом, главным элементом в вашей верстке должен быть контейнер с переопределенным методом fitSystemWindows, содержащим в точности такую же реализацию, как и у нашего listener’а в примере для актуальных версий Android.

class KitkatTransparentSystemBarsFrame @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = -1 ) : FrameLayout(context, attrs, defStyleAttr), KitkatTransparentSystemBarsContainer < override var onSystemInsetsChangedListener: OnSystemInsetsChangedListener = < _, _ ->> override fun fitSystemWindows(insets: Rect?): Boolean < insets ?: return false val desiredBottomInset = InsetUtil.calculateDesiredBottomInset( this, insets.top, insets.bottom, onSystemInsetsChangedListener ) return super.fitSystemWindows(Rect(0, 0, 0, desiredBottomInset)) >//. >

На девайсах с Android 4.4 работает только частичная прозрачность через выставление translucent-флагов:

window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION )

Эти флаги делают системные бары полупрозрачными, добавляя им небольшой градиент, который, к сожалению, невозможно убрать. Однако, градиент можно превратить в полупрозрачную цветную полосу с помощью этой библиотеки: https://github.com/jgilfelt/SystemBarTint . Она не раз выручала нас в прошлом. Последние изменения вносились в библиотеку 5 лет назад, поэтому она откроет свою прелесть лишь истинным ретроградам.

Весь процесс проставления флагов для Kitkat будет выглядеть следующим образом:

fun Activity.setWindowTransparencyKitkat( rootView: KitkatTransparentSystemBarsContainer, listener: OnSystemBarsSizeChangedListener = < _, _ ->> )

С учётом этого, пишем универсальный метод, который умеет делать системные бары прозрачными (или хотя бы полупрозрачными) независимо от того, на устройстве с какой версией Android запускается приложение:

when < Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> setWindowTransparency(::updateMargins) Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT -> setWindowTransparencyKitkat(root_container, ::updateMargins) else -> < /*do nothing*/ >>

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

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

Полный листинг программы и семпл работы вы можете найти в нашем git-репозитории .

Выражаю благодарность студии Surf и Евгению Сатурову за помощь в подготовке материала.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *