Обзор Android 10 Q: новые функции системы
С Android Q, он же Android 10, у вашего смартфона появится много новых возможностей. Рассказываем о них более подробно.


Android Q: десятая версия операционной системы Google
До сих пор версии Android были упорядочены по алфавиту, и именем каждой выступало название какой-нибудь сладости. Так было с Android P, то есть Pie (пирог), O — это Oreo и N — нуга. Но, начиная с версии 2019 года, Google прекратил буквенное обозначение. Да и сладости пропали из названий версий: новая версия Android теперь официально называется Android 10.
Первую бета-версию Google выпустила еще в марте 2019 года. Однако она была доступна только для устройств Google Pixel. Официально новая Android была выпущена третьего сентября 2019 года, именно с этой даты она стала доступна для некоторых устройств.
Общесистемный ввод «тёмной темы»
Некоторые варианты Android и многие приложения для системы в прошлом уже предлагали темную тему, а Android 10 вводит ее сразу для всех программ.
При переключении на новую тему все системные диалоги и настройки автоматически будут отражаться в тёмном режиме. Кроме того, во многих приложениях также изменится цвет системного интерфейса.
Темная тема более приятная и щадящая для глаз в ночное время. Кроме того, в этом режиме ваш смартфон экономит энергию, а аккумулятор разряжается медленнее — это особенно актуально для моделей с OLED-экранами.
Новая функция: управление жестами
Новое управление жестами позволяет выполнять множество действий одним движением пальца. Как и на устройствах с iOS, на Android-смартфонах без кнопок в нижней части экрана теперь отображается полоска, которая служит для управления системой.
Вот основные функции нового управления:
- Свайп вверх закрывает открытое приложение.
- С помощью свайпов влево и вправо можно легко переключаться между открытыми приложениями.
- Кроме того, во всей системе боковой свайп поможет вам вернуться к предыдущему экрану.
- Проведите пальцем вверх из нижнего правого угла, и запустится Google Assistant.
Новое в Android 10: Sound Amplifier (Усилитель звука)
С функцией Sound Amplifier звук вашего смартфона станет ещё круче. Sound Amplifier способен улучшить качество воспроизведения звука вашего смартфона через наушники. Для этого шумы отфильтровываются, а все частоты усиливаются, чтобы обеспечить более чёткое звучание.
Вы можете включить эту функцию для любых устройств, работающих на Android 10. Для этого вам просто нужно установить приложение Sound Amplifier из PlayStore.
Представлена операционная система Android Q
Буквально час назад Google запустила бета-тестирование операционной системы Android Q.
Несмотря на недавние слухи, пока Android Q Beta 1 пока доступна лишь владельцам смартфонов Pixel всех поколений, но вряд ли это надолго.

Новая операционная система, конечно, получит множество новшеств и улучшений. Как всегда, во-первых, не обо всех новинках Google рассказывает во время релиза первой бета-версии, а во-вторых, не обязательно всё то, что есть в бета-версии, доберётся до финальной.
Итак, перечислим то, о чём сейчас говорит Google.
Безопасность
В новой ОС пользователи смогут лучше контролировать, когда приложения могут или не могут определять местоположение пользователя. Кроме прочего, пользователь сможет выбрать, когда именно то или иное ПО будет иметь доступ к местоположению устройства: никогда, только в активном режиме или же постоянно, включая фоновый режим работы.
Также в Android Q расширятся и другие функции, связанные с разрешением доступа приложений к личным данным пользователей.
Поддержка складных устройств
Как уже говорилось ранее, в Android Q появится нативная поддержка складных смартфонов и прочих подобных устройств. Как это будет реализовано на практике, ещё предстоит узнать.

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

Панель настроек
В Android Q разработчики смогут добавлять в свои приложения быстрый доступ к системным настройкам (Wi-Fi, NFC, громкость и так далее), что избавит пользователей от необходимости выходить из приложения и искать нужное в общей панели настроек.

Wi-Fi
В новой ОС появится возможность активировать «адаптивный Wi-Fi», включив режим повышенной производительности и низких задержек, что будет полезно, к примеру, в некоторых играх.
Камера
В Android Q приложения смогут использовать карты глубины, формируемые камерами смартфонов в рамках портерного режима. Это позволит более гибко работать с фотографиями в различных редакторах.
Также в новой системе появится поддержка открытого кодека AV1 и собственный API для работы с устройствами MIDI.
Полезной функцией будет ограничение возможности приложений в любой момент разворачивать своё окно на весь экран из фонового режима. К примеру, если приложению в фоне нужно срочно о чём-то оповестить пользователя, сейчас оно зачастую разворачивается на весь экран. В Android Q у пользователя будет возможность настроить такие оповещения, либо разрешив им разворачиваться в полный экран, либо запретив им это делать.
Android Q

Компания Google в рамках подготовки к весеннему релизу начала тестовые испытания следующей итерации фирменной операционной системы для мобильных устройств, которая, очевидно, получит кодовое наименование Android Q.
Несмотря на то что информации об Android Q в Сети не так уж и много, известно, что обновление ждут серьезные изменения. В частности, одним из преимуществ предстоящей итерации станет нативная поддержка модульной архитектуры Project Treble, которая позволяет отделять низкоуровневое ПО от высокоуровневого и тем самым сокращать время на адаптацию последующих апдейтов операционной системы.
- Google Assistant
- Аккумулятор
- Бета-версии Android
- Китайские смартфоны
- Компания Google
- Мобильные технологии
- Обновления Android
- Операционная система Android
- Смартфоны Honor
- Смартфоны Huawei
Какие смартфоны получили Android 10
Иван Кузнецов

За что пользователи любят Google Pixel? Нет, не только за чистый Android, хотя и он для многих имеет определяющее значение. Раньше купить Google Pixel означало гарантировать себе получение всех обновлений раньше, чем они доберутся до всех остальных. Но, судя по всему, теперь владеть пиксельным смартфоном значит не больше, чем иметь в своём парке гаджетов аппарат китайского бренда. Да и какой в этом смысл, если новые версии Android одновременно выходят и для Google Pixel, и для бюджетников от Redmi?
Google выпустила Android 10. Как скачать
Иван Кузнецов
![]()
В эпоху глобализации стало практически невозможно что-то скрыть. Масла в огонь подливает доступность интернета и обеспеченных им каналов связи, благодаря которым информация из одного конца света долетает до другого за считанные секунды. Этим успешно пользуются инсайдеры, на регулярной основе снабжая нас секретными сведениями о предстоящих новинках тех или иных компаний, большинство из которых, вероятно, предпочли бы скрыть подобного рода сведения. Не стал исключением и Android 10.
Google раскрыла интересные факты об Android 10
Иван Кузнецов
![]()
Внимание пользователей важно для Google не меньше, чем деньги, которые компания зарабатывает массой различных способов. Видимо, по этой причине руководство поискового гиганта приняло волевое решение начать распространять секретную, казалось бы, информацию о Google Pixel 4 задолго до официального релиза. Однако в Маунтин-Вью решили не останавливаться на этом и раскрыли несколько интересных подробностей о разработке Android 10.
Google добавит в Android 10 функцию, которая будет спасать жизни
Иван Кузнецов

Фотосъёмка и технологии слежения за здоровьем – вот два тренда, на которых сегодня выезжают производители мобильной электроники. Но, поскольку измерение пульса и снятие показаний ЭКГ смартфонам совершенно не подходят, на рынок вышли смарт-часы, которые заполнили пустовавшую нишу. Правда, со временем стало ясно, что конкурировать с Apple Watch и Galaxy Watch практически бесполезно. Поэтому производители поумнее начали переносить функции умных часов в смартфоны. Одной из тех, кто поступил таким образом, стала Google.
Google объявила, когда выйдет Android 10
Иван Кузнецов
![]()
В последнее время Google ведёт себя слишком уж откровенно, придавая огласке важную информацию о своих будущих продуктах задолго до их выхода, по сути, отбирая работу у инсайдеров. Никто и ожидать не мог, что компания возьмёт и во всех подробностях расскажет о ключевых функциях предстоящих Google Pixel 4 и Pixel 4 XL. На этом фоне преждевременное раскрытие информации о дате выхода Android 10, которую недавно переименовали из Android Q, выглядит весьма лайтово.
Просто Android 10: Google отказалась от Android Q
Иван Кузнецов

Google постоянно сталкивается с какими-то неприятностями, которые вынуждают её в срочном порядке принимать непопулярные решения. Причём в большинстве случаев их виновником компания становится сама, вгоняя себя в те или иные рамки. Ярчайший тому пример – нейминг новых версий Android, которые Google в течение почти десятка лет называла по имени какого-нибудь десерта, начинавшегося на одну из букв латинского алфавита. Всё шло своим чередом, пока Google не добралась до буквы Q, придумать на которую сладость — та ещё задача.
Какую пасхалку Google спрятала в Android Q
Игорь Букалов

Google очень любит пасхалки. Так, например, компания, приурочивая это к различным праздникам и крупным (или даже мелким) событиям, регулярно встраивает в свой поисковик как для десктопных, так и для мобильных платформ различные игры или скрытые послания. Еще одна такая, ставшая уже традиционной пасхалка, появляется в каждой новой версии мобильной операционки Android. О том, что поисковый гигант утаил от для нас на этот раз, мы расскажем под катом.
Google рассказала, как сделать управление Android удобнее
Иван Кузнецов

Google не имеет привычки что-либо запрещать своим пользователям, если не считать запрета на откат до более ранних версий ОС, введенный в Android Pie. Любая кастомизация, прошивки и руты – все это либо разрешено официально, либо дозволено с немого одобрения Google. Но иногда пользователи ведут себя не так, как хотелось компании, и тогда она показывает им, как нужно. Иногда выходит совсем уж грубо, например, как в случае с напоминаниями, которые связали с Google Assistant. А бывает совсем лайтово, как в случае с жестовым управлением.
Что нового в Android Q Beta 6
Игорь Букалов

Android Q на финишной прямой. Только в прошлом месяце мы получили Beta 5, а уже сегодня Google обновляет свою мобильную операционную систему до новой бета-версии, которая в то же время является последней. В следующий раз мы уже увидим официальный публичный релиз, а пока давайте разбираться, что нового подарило нам очередное обновление «зелёного робота».
Huawei рассказала о новых функциях EMUI 10
Артем Сутягин

Время, когда сеть словно улей гудела о войне между Huawei и правительством США, закончилось относительно малой кровью (или временным перемирием). Сейчас непонятно, будет ли Huawei пытаться выпустить Hongmeng, займется ОС Аврора или будет работать над Android дальше, но на днях представители компании рассказали о ключевых особенностях EMUI 10.
Android insets: разбираемся со страхами и готовимся к Android Q
Android Q — это десятая версия Android с 29-м уровнем API. Одна из главных идей новой версии это концепция edge-to-edge, когда приложения занимают весь экран, от нижней рамки до верхней. Это значит, что Status Bar и Navigation Bar должны быть прозрачными. Но, если они прозрачны, то системный UI нет — он перекрывает интерактивные компоненты приложения. Эта проблема решается с помощью insets.
Мобильные разработчики избегают insets, они вызывают у них страх. Но в Android Q обойти insets не удастся — придется их изучить и применять. На самом деле, в insets нет ничего сложного: они показывают, какие элементы экрана пересекаются с системным интерфейсом, и подсказывают, как переместить элемент, чтобы он не конфликтовал с системным UI. О том, как работают insets и чем они полезны, расскажет Константин Цховребов.
Константин Цховребов (terrakok) работает в Redmadrobot. Занимается Android 10 лет и накопил много опыта в разных проектах, в которых не было места insets, их всегда удавалось как-то обойти. Константин расскажет о долгой истории избегания проблемы insets, об изучении и борьбе с Android. Рассмотрит типичные задачи из своего опыта, в которых можно было бы применить insets, и покажет, как перестать бояться клавиатуры, узнавать ее размер и реагировать на появление.
Примечание. Статья написана на основе доклада Константина на Saint AppsConf 2019. В докладе использованы материалы из нескольких статей по insets. Ссылка на эти материалы в конце.
Типичные задачи
Цветной Status Bar. Во многих проектах дизайнер рисует цветной Status Bar. Это стало модно, когда появился Android 5 с новым Material Design.

Как покрасить Status Bar? Элементарно — добавляем colorPrimaryDark и цвет подставляется.
Для Android выше пятой версии (API от 21 и выше) можно задать специальные параметры в теме:
Разноцветный Status Bar. Иногда дизайнеры на разных экранах рисуют Status Bar разного цвета.

Ничего страшного, самый простой способ, который работает, это разные темы в разных activity.
Способ интереснее — менять цвета прямо в runtime.
override fun onCreateView(. ): View
Параметр меняется через специальный флаг. Но главное не забыть сменить цвет обратно, когда пользователь выходит с экрана назад.
Прозрачный Status Bar. Это сложнее. Чаще всего прозрачность связана с картами, потому что именно на картах лучше всего видно прозрачность. В этом случае также как и раньше ставим специальный параметр:
Здесь, конечно, есть известная хитрость — поставить отступ выше, иначе на иконку наложится Status Bar и будет некрасиво.

Но на других экранах все ломается.

Как решить проблему? Первый способ, который приходит в голову, это разные activity: у нас разные темы, разные параметры, они по-разному работают.
Работа с клавиатурой. Мы избегаем insets не только со Status Bar, но и при работе с клавиатурой.

Вариант слева никто не любит, но, чтобы превратить его в вариант справа, есть простое решение.
Теперь activity может менять свой размер, когда появляется клавиатура. Работает просто и сурово. Но не забудьте еще одну хитрость — обернуть все в ScrollView . Вдруг клавиатура займет весь экран и останется маленькая полоска сверху?
Бывают случаи сложнее, когда мы хотим изменить верстку при появлении клавиатуры. Например, иначе расставить кнопки или спрятать логотип.

Мы наставили столько костылей с клавиатурой и прозрачным Status Bar, теперь нас сложно остановить. Идем на StackOverflow и копируем прекрасный код.
boolean isOpened = false; public void setListenerToRootView() < final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content); activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() < @Override public void onGlobalLayout() < int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight(); if (heightDiff >100) < // 99% of the time the height diff will be due to a keyboard. Toast.makeText(getApplicationContext(), "Gotcha. softKeyboardup", 0).show(); if (isOpened == false) < //Do two things, make the view top visible and the editText smaller >isOpened = true; > else if (isOpened == true) < Toast.makeText(getApplicationContext(), "softkeyborad Down. ", 0).show(); isOpened = false; >> >); >
Здесь даже рассчитана вероятность появления клавиатуры. Код работает, в одном из проектов мы его даже использовали, но давно.
Много костылей в примерах связаны с использованием разных activity. Но они плохи не только тем, что это костыли, но и другими причинами: проблемой «холодного запуска», асинхронностью. Подробнее проблемы я описал в статье «Лицензия на вождение болида, или почему приложения должны быть Single-Activity». К тому же, в документации Google указывает, что рекомендуемый подход — это Single Activity приложение.
Что мы делали до Android 10
Мы (в Redmadrobot) разрабатываем достаточно качественные приложения, но долго избегали insets. Как же нам удавалось их избегать без больших костылей и в одном activity?
Примечание. Скриншоты и код взяты из моего pet-проекта GitFox.
Представим экран приложения. Когда мы разрабатывали наши приложения, никогда не задумывались что внизу может быть прозрачный Navigation Bar. Внизу черная полоска? Ну и что, пользователи привыкли.

Сверху мы изначально ставим параметр, что Status Bar черный с прозрачностью. Как это выглядит с точки зрения верстки и кода?

На рисунке абстракция: красный блок это activity приложения, синий это фрагмент с ботом навигации (с Tab’ами), а внутри него переключаются зеленые фрагменты с контентом. Видно, что Toolbar не находится под Status Bar. Как мы этого добились?
В Android есть хитрый флаг fitSystemWindow . Если установить его в «true», то контейнер сам себе добавит padding, чтобы внутри него ничего не попадало под Status Bar. Я считаю, что этот флаг — официальный костыль от Google для тех, кто боится insets. Используйте, все будет относительно хорошо работать и без insets.
Флаг FitSystemWindow=”true” добавляет padding контейнера, который вы указали. Но важна иерархия: если кто-то из родителей выставил этот флаг в «true», дальше его распространение учитываться не будет, потому что контейнер уже применил отступы.
Флаг работает, но появляется другая проблема. Представим экран с двумя табами. При переключении запускается транзакция, которая вызывает replace фрагмента один на другой, и все ломается.

У второго фрагмента тоже выставлен флаг FitSystemWindow=”true” , и подобного не должно случаться. Но произошло, почему? Ответ в том, что это костыль, а он иногда не работает.
Но мы нашли решение на Stack Overflow: использовать для своих фрагментов корневым не FrameLayout , а CoordinatorLayout . Он создан для других целей, но здесь работает.
Почему работает?
Посмотрим в исходниках что происходит в CoordinatorLayout .
@Override public void onAttachedToWindow() < super.onAttachedToWindow(); . if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) < // We're set to fitSystemWindows but we haven't had any insets yet. // We should request a new dispatch of window insets ViewCompat.requestApplyInsets(this); >. >
Видим красивый комментарий, что здесь нужны insets, а их нет. Надо их перезапросить, когда мы запрашиваем окно. Мы разобрались, что внутри как-то работают insets, но мы с ними не хотим работать и оставляем Coordinator .
Весной 2019 мы разрабатывали приложение, в это время как раз прошел Google I/O. Мы еще не со всем разобрались, поэтому продолжали держаться за предрассудки. Но мы замечали, что переключение Tab’ов внизу какое-то медленное, потому что у нас сложная верстка, нагруженный UI. Находим простой способ это решить — поменять replace на show/hide , чтобы каждый раз заново не создавать верстку.

Меняем, и опять ничего не работает — все сломалось! Видимо просто так костыли разбросать не получится, надо понять, почему они работали. Изучаем код и оказывается, что любая ViewGroup тоже умеет работать с insets.
@Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) < insets = super.dispatchApplyWindowInsets(insets); if (!insets.isConsumed()) < final int count = getChildCount(); for (int i = 0; i < count; i++) < insets = getChildAt(i).dispatchApplyWindowInsets(insets); if (insets.isConsumed()) < break; >> > return insets; >
Внутри такая логика: если хоть кто-то insets уже обработал, то все последующие View внутри ViewGroup их не получат. Что это значит для нас? Покажу на примере нашего FrameLayout, который переключает Tab’ы.

Внутри есть первый фрагмент, у которого выставлен флаг fitSystemWindow=”true” . Это значит, что первый фрагмент обрабатывает insets. После этого вызываем HIDE первого фрагмента и SHOW второго. Но первый остается в верстке — у него View осталась, просто скрыта.
Контейнер идет по своим View: берет первый фрагмент, дает ему insets, а у него fitSystemWindow=”true” — он взял их и обработал. Прекрасно, FrameLayout посмотрел, что insets обработаны, и второму фрагменту не отдает. Все работает как надо. Но нас это не устраивает, что же делать?
Пишем собственную ViewGroup
Мы пришли из Java, а там ООП во всей красе, поэтому решили наследоваться. Мы написали свою собственную ViewGroup, у которой переопределили метод dispatchApplyWindowInsets . Он работает так, как нам нужно: всем дочерним элементам всегда отдает insets, которые пришли, независимо от того, обработали мы их или нет.
class WindowInsetFrameLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0 ) : FrameLayout(context, attrs, defStyleRes) < override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets < for (child in (0 until childCount)) < getChildAt(child).dispatchApplyWindowInsets(insets) >return insets.consumeSystemWindowInsets() > >
Кастомная ViewGroup работает, проект тоже — все, как нам надо. Но до общего решения еще не дотягивает. Когда мы разобрались, что нам рассказали на Google IO, то поняли, что дальше так действовать не получится.
Android 10
Android 10 нам показал две важные концепции UI, которых строго рекомендовано придерживаться: edge-to-edge и Gestural Navigation.
Edge-to-edge. Эта концепция говорит о том, что контент приложения должен занимать все возможное пространство на экране. Для нас, как разработчиков, это значит, что приложения должны размещаться под системными панелями Status Bar и Navigation Bar.

Раньше мы могли это как-то игнорировать или размещаться только под Status Bar.
Что касается списков, то они должны прокручиваться не только до последнего элемента, но и дальше, чтобы не остаться под Navigation Bar.

Gestural Navigation. Этовторая важная концепция — навигация жестами. Она позволяет управлять приложением пальцами с края экрана. Режим жестов нестандартный, все выглядит иначе, но теперь можно не переключаться между двумя разными Navigation Bar.
В этот момент мы поняли, что все не так просто. Не получится дальше избегать insets, если мы хотим разрабатывать качественные приложения. Пора изучать документацию и разбираться, что же такое insets.
Insets. Что о них нужно знать?
Insets были созданы, чтобы пугать разработчиков.
Им это прекрасно удается, начиная с момента появления в Android 5.
Конечно, все не так страшно. Концепция insets проста — они сообщают о наложении системного UI на экран приложения. Это могут быть Navigation Bar, Status Bar или клавиатура. Клавиатура это тоже обычный системный UI, который накладывается поверх приложения. Ее не надо пытаться обрабатывать никакими костылями, только insets.
Цель insets — разрешать конфликты. Например, если над нашей кнопкой есть еще элемент, мы можем сдвинуть кнопку, чтобы пользователь мог продолжать пользоваться приложением.
Для обработки inset используйте Windowlnsets для Android 10 и WindowInsetsCompat для остальных версий.
В Android 10 есть 5 разных видов insets и один «бонусный», который называется не inset, а иначе. Разберемся со всеми видами, потому что большинство знает только один — System Window Insets.
System Window Insets
Появились в Android 5.0 (API 21). Получаются методом getSystemWindowInsets() .
Это основной тип insets, с которым надо научиться работать, потому что остальные работают аналогично. Они нужны, чтобы обрабатывать Status Bar, в первую очередь, а потом Navigation Bar и клавиатуру. Например, они решают проблему когда Navigation Bar находится над приложением. Как на картинке: кнопка осталась под Navigation Bar, пользователь не может на нее кликнуть и очень недоволен.

Tappable element insets
Появились только в Android 10. Получаются методом getTappableElementInsets() .
Эти insets полезны только для обработки разных режимов Navigation Bar. Как говорит сам Крис Бейн, можно забыть про этот тип insets и обходиться только System Window Insets. Но если вы хотите приложение крутое на 100%, а не на 99,9%, стоит им воспользоваться.
Посмотрите на картинку.

Сверху малиновым цветом отмечены System Window Insets, которые придут в разных режимах Navigation Bar. Видно, что и справа, и слева они равны Navigation Bar.
Вспомним как работает навигация жестами: при режиме справа мы никогда не нажимаем на новую панель, а всегда тянем пальцами снизу вверх. Это значит, что кнопки можно и не убирать. Мы же можем продолжать нажимать на нашу кнопку FAB (Floating Action Button), никто мешать не будет. Поэтому именно TappableElementInsets придут пустые, потому что FAB двигать необязательно. Но если сдвинем немного выше, ничего страшного.
Разница появляется только при навигации жестами и прозрачном Navigation Bar (color adaptation). Это не очень приятная ситуация.

Все будет работать, но выглядит неприятно. Пользователь может быть смущен близким расположением элементов. Можно объяснить, что один для жестов, а другой для нажатия, но все равно не красиво. Поэтому либо поднимаем FAB выше, либо оставляем справа.
System Gesture Insets
Появились только в Android 10. Получаются методом getSystemGestureInsets() .
Эти insets связаны с навигацией жестами. Изначально предполагалось, что системный UI рисуется поверх приложения, но System Gesture Insets говорят, что не UI отрисовывается, а система сама обрабатывает жесты. Они описывают те места, где система по умолчанию будет обрабатывать жесты.
Области этих insets примерно те, что отмечены на схеме желтым.

Но хочу предупредить — они не всегда будут такие. Мы никогда не узнаем, какие новые экраны придумает Samsung и другие производители. Уже есть девайсы, в которых экран со всех сторон. Возможно, insets будут совсем не там, где мы ожидаем. Поэтому нужно с ними работать как с некоторой абстракцией: есть такие insets в System Gesture Insets, в которых система сама обрабатывает жесты.
Эти insets можно переопределить. Например, вы разрабатываете редактор фотографий. В некоторых местах вы сами хотите обрабатывать жесты, даже если они рядом с краем экрана. Укажите системе, что точку на экране в углу фотографии будете обрабатывать самостоятельно. Его можно переопределить, сказав системе: «Квадрат вокруг точки я буду всегда обрабатывать сам».
Mandatory system gesture insets
Появились только в Android 10. Это подтип System Gesture Insets, но они не могут быть переопределены приложением.
Мы используем метод getMandatorySystemGestureInsets() , чтобы определить область, где они не будут работать. Это сделано намеренно, чтобы нельзя было разработать «безвыходное» приложение: переопределить жест навигации снизу вверх, который позволяет выйти из приложения на главный экран. Здесь система всегда обрабатывает жесты сама.

Не обязательно он будет снизу на устройстве и не обязательно таких размеров. Работайте с этим, как с абстракцией.
Это были относительно новые виды insets. Но есть те, что появились даже до Android 5 и назывались иначе.
Stable Insets
Появились с Android 5.0 (API 21). Получаются методом getStableInsets() .
Даже опытные разработчики не всегда могут сказать, для чего они нужны. Раскрою секрет: эти insets полезны только для полноэкранных приложений: видеоплееры, игры. Например, в режиме проигрывания прячется любой системный UI, в том числе Status Bar, который перемещается за край экрана. Но стоит коснуться экрана, как Status Bar появится сверху.
Если какую-нибудь кнопку «НАЗАД» вы ставили в верхней части экрана, при этом она правильно обрабатывает System Window Insets, то при каждом Tab на экран сверху появляется UI, а кнопка прыгает вниз. Если некоторое время не трогать экран, Status Bar исчезает, кнопка подпрыгивает вверх, потому что System Window Insets исчезли.
Для таких случаев как раз подходят Stable Insets. Они говорят, что сейчас никакой элемент не отрисовывается над вашим приложением, но может это сделать. С этим insets можно заранее узнать величину Status Bar в этом режиме, например, и расположить кнопку там, где вам хочется.
Примечание. Метод getStableInsets() появился только с API 21. Раньше было несколько методов для каждой стороны экрана.
Мы рассмотрели 5 видов insets, но есть еще один, который не относится к insets напрямую. Он помогает разобраться с челками и вырезами.
Челки и вырезы
Экраны давно уже не квадратные. Они продолговатые, с одной или несколькими вырезами под камеры и челками со всех сторон.

До 28 API мы не могли их обработать. Приходилось догадываться через insets, что там происходит. Но с 28 API и дальше (с предыдущего Android) официально появился класс DisplayCutout . Он находится в тех же insets, из которых можно достать все остальные типы. Класс позволяет узнать расположение и размер артефактов.
Кроме информации о расположении для разработчика предоставлен набор флагов у WindowManager.LayoutParams . Они позволяют включать разное поведение вокруг вырезов. Ваш контент может отображаться вокруг них, а может не отображаться: в пейзажном и в портретном режиме по-разному.
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT . В портретном — есть, а в пейзажном — черная полоса по умолчанию.
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER . Нет совсем — черная полоса.
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES — всегда есть.
insets.displayCutout .boundingRects .forEach < rect ->. >
К нам приходит callback с insets с массивом displayCutout . Дальше можем по нему пробежать, обработать все вырезы и челки, которые есть в приложении. Подробнее о том, как работать с ними, можно узнать в статье.
Разобрались с 6 видами insets. Теперь поговорим о том, как это работает.
Как это работает
Insets нужны, в первую очередь, когда поверх приложения что-то рисуется, например, Navigation Bar.

Без insets у приложения не будет прозрачных Navigation Bar и Status Bar. Не удивляйтесь, что вам не приходят SystemWindowInsets , такое бывает.
Распространение insets
Вся иерархия UI выглядит как дерево, на концах которого есть View. Узлы — это обычно ViewGroup. Они также наследуются от View, поэтому insets им приходят специальным методом dispatchApplyWindowInsets . Начиная с корневой View, система рассылает insets по всему дереву. Посмотрим, как работает в данном случае View.

Система вызывает на ней метод dispatchApplyWindowInsets , в который приходят insets. Обратно эта View должна что-то вернуть. Что сделать, чтобы обработать это поведение?
Для простоты рассмотрим только WindowInsets.
Обработка insets
В первую очередь кажется логичным переопределение метода dispatchApplyWindowInsets и реализация внутри собственной логики: пришли insets, мы реализовали все внутри.
Если открыть класс View и посмотреть на Javadoc, написанный поверх этого метода, можно увидеть: «Не переопределяйте этот метод!»
Insets обрабатываем через делегирование или наследование.
Используйте делегирование. Google предоставил возможность выставить собственного делегата, который отвечает за обработку insets. Установить его можно через метод setOnApplyWindowInsetsListener(listener) . Мы ставим callback, который обрабатывает эти insets.
Если нам это почему-то не подходит, можно наследоваться от View и переопределить другой метод onApplyWindowInsets(WindowInsets insets) . В него подставим собственные insets.
Google не стал выбирать между делегированием и наследованием, а позволил сделать все вместе. Это здорово, потому что нам не придется переопределять все Toolbar или ListView, чтобы обработать insets. Мы можем брать любую готовую View, даже библиотечную, и сами устанавливать туда делегат, который обработает insets без переопределения.
Что же нам возвращать?
Зачем что-то возвращать?
Для этого надо разобраться, как работает распределение insets, то есть ViewGroup. Что она делает внутри себя. Рассмотрим подробней поведение по умолчанию на примере, который я показывал раньше.
Системные insets пришли сверху и снизу, например, по 100 пикселей. ViewGroup отправляет их первой View, которая у него есть. Эта View их как-то обработала и возвращает insets: сверху уменьшает до 0, а снизу оставляет. Сверху она добавила padding или margin, а снизу не трогала и об этом сообщила. Дальше ViewGroup передает insets второй View.

Следующая View обрабатывает и отдает insets. Теперь ViewGroup видит, что insets обработаны и сверху, и снизу — больше ничего не осталось, все параметры по нулям.
Третья View даже не узнает, что были какие-то insets и что-то происходило. ViewGroup вернет insets назад тому, кто их выставил.
Когда мы начали разбираться с этой темой, то записали себе идею — всегда возвращать те же insets, что пришли. Мы хотели, чтобы не происходило такой ситуации, когда какие-то View даже не узнали, что были insets. Идея выглядела логичной. Но оказалось, что нет. Google не зря добавил в insets поведение, как в примере.
Это нужно, чтобы до всей иерархии View всегда доходили insets и всегда можно было с ними работать. В этом случае не будет ситуации когда мы переключаем два фрагмента, один кто-то обработал, а второй еще не получил. В разделе с практикой мы вернемся к этому моменту.
Практика
С теорией закончили. Посмотрим, как это выглядит в коде, потому что в теории всегда все гладко. Будем использовать AndroidX.
Примечание. В GitFox все уже реализовано в коде, полная поддержка всех плюшек из нового Android. Чтобы не проверять версии Android и не искать нужный тип Insets, всегда используйте вместо view.setOnApplyWindowInsetsListener < v, insets ->. > версию из AndroidX — ViewCompat.setOnApplyWindowInsetsListener(view) < v, insets ->. > .
Есть экран с темным Navigation Bar. Он не накладывается сверху ни на один элемент. Все так, как мы привыкли.

Но оказывается, что теперь нам надо сделать его прозрачным. Как?
Включаем прозрачность
Самое простое — указать в теме, что Status Bar и Navigation Bar прозрачные.
В этот момент все начинает накладываться друг на друга и сверху, и снизу.
Интересно, что здесь два флага, а вариантов всегда 3. Если укажете прозрачность у Navigation Bar, Status Bar станет прозрачным сам собой — такое ограничение. Можно первую строчку не писать, но я всегда люблю ясность, поэтому пишу 2 строки, чтобы последователи могли понять, что происходит, а не копаться внутри.
Если выберете этот способ, то Navigation Bar и Status Bar станут черными с прозрачностью. Если приложение белого цвета, то добавить цвета можно уже только из кода. Как это сделать?
Включаем прозрачность с цветом
Хорошо, что у нас Single Activity приложение, поэтому в одном activity проставляем два флага.
rootView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Флагов там очень много, но именно эти два помогут сделать прозрачность у Navigation Bar и Status Bar. Цвет можно указать через тему.
Это странное поведение Android: что-то через тему, а что-то через флаги. Но мы можем указывать параметры как в коде, так и в теме. Это не Android такой плохой, просто на старых версиях Android флаг, указанный в теме, будет игнорироваться. navigationBarColor подсветит, что на Android 5 такого нет, но все соберется. В GitFox я именно из кода установил цвет Navigation Bar.
Мы провели махинации — указали, что Navigation Bar белый с прозрачностью. Теперь приложение выглядит так.

Что может быть проще, чем обработать insets?
ViewCompat .setOnApplyWindowInsetsListener(bottomNav) < view, insets ->view.updatePadding(bottom = insets.systemWindowInsetBottom) insets >
Берем метод setOnApplyWindowInsetsListener и передаем в него bottomNav . Говорим, что когда придут insets, установи снизу padding, который пришел в systemWindowInsetBottom . Мы хотим, чтобы он был под ним, но все наши кликабельные элементы оказались сверху. Возвращаем полностью insets, чтобы все другие View в нашей иерархии тоже их получили.
Все выглядит отлично.

Но есть подвох. Если в верстке мы указали какой-то padding у Navigation Bar снизу, то здесь его затерли — выставили updatePadding равным insets. Наша верстка выглядит не так, как хотелось бы.
Чтобы сохранить значения из верстки, надо сначала сохранить то, что в нижнем padding. Позже, когда придут insets, сложить и выставить результирующие значения.
val bottomNavBottomPadding = bottomNav.paddingBottom ViewCompat .setOnApplyWindowInsetsListener(bottomNav) < view, insets ->view.updatePadding( bottom = bottomNavBottomPadding + insets.systemWindowInsetBottom ) Insets >
Так писать неудобно: везде в коде, где применяете insets, надо сохранять значение из верстки, потом их складывать на UI. Но есть Kotlin, и это прекрасно — можно написать собственное расширение (extension), которое все это сделает за нас.
Добавим Kotlin!
Запоминаем initialPadding и отдаем обработчику, когда приходят новые insets (вместе с ними). Это поможет их как-то вместе складывать или строить какую-то логику сверху.
fun View.doOnApplyWindowInsets(block: (View, WindowInsetsCompat, Rect) -> WindowInsetsCompat) < val initialPadding = recordInitialPaddingForView(this) ViewCompat.setOnApplyWindowInsetsListener(this) < v, insets ->block(v, insets, initialPadding) > requestApplyInsetsWhenAttached() > private fun recordInitialPaddingForView(view: View) = Rect(view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom) Теперь все проще. bottomNav.doOnApplyWindowInsets < view, insets, padding ->view.updatePadding( bottom = padding.bottom + insets.systemWindowInsetBottom ) insets >
Мы должны переопределить лямбду. В ней есть не только insets, но и padding’и. Мы их можем сложить не только снизу, но и сверху, если это Toolbar, который обрабатывает Status Bar.
Кое о чем забыли!
Здесь есть вызов непонятного метода.
fun View.doOnApplyWindowInsets(block: (View, WindowInsetsCompat, Rect) -> WindowInsetsCompat) < val initialPadding = recordInitialPaddingForView(this) ViewCompat.setOnApplyWindowInsetsListener(this) < v, insets ->block(v, insets, initialPadding) > requestApplyInsetsWhenAttached() > private fun recordInitialPaddingForView(view: View) = Rect(view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom)
Связано это с тем, что нельзя просто надеяться на то, что система вам пришлет insets. Если мы создаем View из кода или устанавливаем какой-то InsetsListener чуть позже, система сама может не передать последнее значение.
Мы должны проверить, что когда View находится на экране, сказали системе: «Пришли insets, мы хотим их обработать». Мы выставили Listener и должны сделать запрос requestsApplyInsets ».
fun View.requestApplyInsetsWhenAttached() < if (isAttachedToWindow) < requestApplyInsets() >else < addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener < override fun onViewAttachedToWindow(v: View) < v.removeOnAttachStateChangeListener(this) v.requestApplyInsets() >override fun onViewDetachedFromWindow(v: View) = Unit >) > >
Если же мы создали View кодом и еще не прикрепили к нашей верстке, то должны подписаться на момент когда это сделаем. Только тогда запрашиваем insets.
Но опять же, есть Kotlin: написали простое расширение и больше нам об этом не надо думать.
Доработка RecyclerView
Теперь поговорим о доработке RecyclerView. Последний элемент попадает под Navigation Bar и остается снизу — некрасиво. На него еще и кликать неудобно. Если это еще не новая панель, а старая, большая, то вообще весь элемент может скрыться под ней. Что делать?

Первая идея — добавить элемент снизу, а высоту выставлять по размеру insets. Но если у нас приложение с сотней списков, то каждый список как-то придется подписывать на insets, добавлять туда новый элемент и выставлять высоту. Кроме того RecyclerView — асинхронная, неизвестно, когда она сработает. Проблем много.
Но есть очередной официальный хак. На удивление, он работает качественно.
recyclerView.doOnApplyWindowInsets < view, insets, padding ->view.updatePadding( bottom = padding.bottom + insets.systemWindowInsetBottom ) >
Есть такой флаг в верстке clipToPadding . По умолчанию он всегда «true». Это говорит о том, что не надо рисовать элементы, которые оказываются там, где выставлен padding. Но если выставить clipToPadding=»false» , то рисовать можно.
Теперь, если выставить padding снизу, в RecyclerView он будет работать так: снизу выставлен padding, и элемент рисуется поверх него, пока мы не прокрутили. Как только дошли до конца RecyclerView прокрутит элементы до того положения, которое нам нужно.
Выставив один такой флаг мы можем работать с RecyclerView как с обычной View. Не надо думать, что там есть элементы, которые прокручиваются — просто выставляем padding снизу, как, например, в Bottom Navigation Bar.
Сейчас мы всегда возвращали insets, будто их не обработали. К нам пришли insets целиком, мы с ними что-то сделали, выставили padding’и и вернули опять все insets целиком. Это нужно, чтобы любая ViewGroup всегда передавала эти insets всем View. Это работает.
Баг в приложениях
Во многих приложениях в Google Play, которые уже обработали insets, я заметил маленький баг. Сейчас о нем расскажу.
Есть экран с навигацией в подвале. Справа тот же экран на котором показана иерархия. Зеленый фрагмент отображает контент на экране, внутри у него RecyclerView.

Кто здесь обработал insets? Toolbar: он применил padding сверху чтобы его контент сместился под Status Bar. Соответственно, Bottom Navigation Bar применил insets снизу и приподнялся над Navigation Bar. А RecyclerView никак не обрабатывает insets, он же под них не попадает, ему не нужно обрабатывать insets — все правильно сделано.

Но тут оказывается, что зеленый фрагмент RecyclerView мы хотим использовать в другом месте, где уже нет Bottom Navigation Bar. В этом месте RecyclerView уже начинает обрабатывать insets снизу. Мы должны к нему применить padding чтобы правильно прокручиваться из-под Navigation Bar. Поэтому в RecyclerView тоже добавляем обработку insets.

Идем обратно на наш экран, где все обрабатывают insets. Помните, никто не сообщает о том, что он их обработал?

Видим такую ситуацию: RecyclerView обработал insets снизу, хотя не доходит до Navigation Bar — внизу появился пробел. Я это замечал в приложениях, причем довольно крупных и популярных.

Раз все так, мы помним, что возвращаем все insets, чтобы обработать. Значит (оказывается!) надо сообщать о том, что insets обработаны: Navigation Bar должен сообщить о том, что обработала insets. До RecyclerView они не должны дойти.

Для этого в Bottom Navigation Bar устанавливаем InsetsListener . Внутри вычитаем bottom + insets , что они не обработаны, возвращаем 0.
doOnApplyWindowInsets < view, insets, initialPadding ->view.updatePadding( bottom = initialPadding.bottom + insets.systemWindowInsetBottom ) insets.replaceSystemWindowInsets( Rect( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, 0 ) ) >
Возвращаем новые insets, у которых все старые параметры равны, а bottom + insets равен 0. Вроде все хорошо. Запускаем и снова ерунда — RecyclerView все равно почему-то обрабатывает insets.

Я не сразу понял, что проблема в том, что контейнер для этих трех View — это LinearLayout. Внутри них Toolbar, фрагмент с RecyclerView и внизу Bottom Navigation Bar. LinearLayout берет свои дочерние элементы по порядку и применяет к ним insets.
Получается, что Bottom Navigation Bar будет последним. Он сообщил кому-то, что обработал все insets, но уже поздно. Все insets обработались сверху вниз, RecyclerView их уже получил, и нас это не спасает.

Что делать? LinearLayout работает не так, как я хочу, он сверху вниз их передает, а мне нужно получить сначала нижний.
Все переопределим
Во мне сыграл Java-разработчик — надо все переопределить! Хорошо, сейчас dispatchApplyWindowInsets переопределим, поставим МyLinearLayout , который всегда будет идти снизу вверх. Он будет сначала отправлять insets Bottom Navigation Bar, а потом всем остальным.
@Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) < insets = super.dispatchApplyWindowInsets(insets); if (!insets.isConsumed()) < final int count = getChildCount(); for (int i = 0; i < count; i++) < insets = getChildAt(i).dispatchApplyWindowInsets(insets); if (insets.isConsumed()) < break; >> > return insets; >
Но я вовремя остановился, вспомнив комментарий, что не надо переопределять этот метод.
Здесь чуть выше есть спасение: ViewGroup проверяет на делегата, который обрабатывает insets, потом вызывает суперметод у View и включает собственную обработку. В этом коде мы получаем insets, и если они еще не были обработаны, начинаем стандартную логику по нашим дочерним элементам.
Я написал простое расширение. Оно позволяет, применив InsetsListener к одной View, сказать, кому эти insets передать.
fun View.addSystemBottomPadding( targetView: View = this ) < doOnApplyWindowInsets < _, insets, initialPadding ->targetView.updatePadding( bottom = initialPadding.bottom + insets.systemWindowInsetBottom ) insets.replaceSystemWindowInsets( Rect( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, 0 ) ) > >
Здесь targetView равна по умолчанию той же самой View, поверх которой мы применяем addSystemBottomPadding . Мы можем ее переопределить.
На мой LinearLayout я повесил такой обработчик, передав как targetView — это мой Bottom Navigation Bar.
- Сначала он даст insets Bottom Navigation Bar.
- Тот обработает insets, вернет ноль bottom.
- Дальше, по умолчанию, они пойдут сверху вниз: Toolbar, фрагмент с RecyclerView.
- Потом, возможно, он опять отправит insets Bottom Navigation Bar. Но это уже не важно, все будет и так прекрасно работать.

Важное
Несколько важных вещей, о которых надо помнить.
Клавиатура — это системный UI поверх вашего приложения. Не надо относиться к ней по-особенному. Если посмотреть на Google-клавиатуру, то это не просто Layout с кнопками, на которые можно нажимать. Есть сотни режимов этой клавиатуры: поиск гифок и мемов, голосовой ввод, смена размеров от 10 пикселей в высоту до размеров экрана. Не думайте о клавиатуре, а подписывайтесь на insets.
Navigation Drawer все еще не поддерживает навигацию жестами. На Google IO обещали, что все будет работать из коробки. Но в Android 10 Navigation Drawer до сих пор это не поддерживает. Если обновиться до Android 10 и включить навигацию жестами, Navigation Drawer отвалится. Теперь надо кликать на меню гамбургер, чтобы он появился, либо стечение случайных обстоятельств позволяет его вытянуть.
В пре-альфа версии Android Navigation Drawer работает, но я не рискнул обновляться — это же пре-альфа. Поэтому, даже если вы поставите из репозитория последнюю версию GitFox, там есть Navigation Drawer, но его не вытянуть. Как только выйдет официальная поддержка, я обновлю и все будет прекрасно работать.
Чек-лист подготовки к Android 10
Ставьте прозрачность Navigation Bar и Status Bar с начала проекта. Google строго рекомендует поддерживать прозрачный Navigation Bar. Для нас это важная практическая часть. Если проект работает, а вы не включили — выберете время на поддержку Android 10. Включайте сначала их в теме, как translucent, и исправляйте верстку, где она сломалась.
Добавьте расширения на Kotlin — с ними проще.
Добавьте ко всем Toolbar сверху padding. Toolbars всегда наверху, так и надо поступать.
У всех RecyclerView добавьте padding снизу и возможность прокручивать через clipToPadding=»false» .
Вспомните про все кнопки на краях экрана(FAB). FAB’ы, скорее всего, окажутся не там, где вы ожидаете.
Не переопределяйте и не делайте собственные реализации для всех LinearLayout и других подобных кейсов. Сделайте трюк, как в GitFox, или возьмите мое готовое расширение в помощь.
Проверьте жесты на краях экрана у кастомных View. Navigation Drawer это не поддерживает. Но не так сложно его поддерживать руками, переопределить insets для жестов на экране с Navigation Drawer. Возможно, у вас есть редакторы картинок, где жесты работают.
Прокрутка в ViewPager работает не с краю, а только с центра вправо-влево. Google говорит, что это нормально. Если тянуть ViewPager с края, то это воспринимается как нажатие кнопки «Назад».
В новом проекте сразу включайте все прозрачности. Можно сказать дизайнерам, что у вас нет Navigation Bar и Status Bar. Весь квадрат — это ваш контент приложения. А разработчики уже разберутся с тем, где и что поднять.
Полезные ссылки:
- @rmr_spb в телеграме — записи внутренних митапов Redmadrobot SPb.
- Весь исходный код в этой статье и даже больше в GitFox. Там есть отдельная ветка Android 10, где по коммитам можно посмотреть, как я дошел до всего этого и как поддержал новый Android.
- Библиотека Insetter Криса Бейна. Она содержит 5-6 extensions, которые я показывал. Чтобы использовать в своем проекте, обратитесь в библиотеку, скорее всего она переедет в Android Jetpack. Я их развил у себя в проекте и, мне кажется, они стали лучше.
- Статья Криса Бейна.
- Статья от компании FunCorn, в которой они рассказали, как работать с вырезами и челками.
- Статья «Why would I want to fitsSystemWindows?» Яна Лэйка.
Константин Цховребов постоянный спикер AppConf, на youtube-канале конференции можно найти несколько его докладов. Но в организации конференции мы не полагаемся только на проверенные временем подходы, а все время придумываем что-то новое. Фишки осенней AppsConf сейчас как раз в разработке, но рассказывать о них будет уже новый руководитель Программного комитета Евгений Суворов. Подписывайтесь на рассылку, телеграм, stay tuned!
- Блог компании Конференции Олега Бунина (Онтико)
- Программирование
- Разработка мобильных приложений
- Разработка под Android
- Дизайн мобильных приложений
