«Поясняем за чёлку» в Android P. Что делать с Android Cutout?

в 7:05, , рубрики: android, android cutout, Android P, Android sdk, design, layout, Блог компании FunCorp, дизайн мобильных приложений, разработка мобильных приложений, Разработка под android

Горел сентябрь 2007 года. Шёл сентябрь 2017 года, Apple вернули моду на чёлку, представив iPhone X. Неудивительно, что наши друзья из Китая, недолго думая, скопировали этот дизайн у Apple (хотя самая первая мини-чёлка была ещё в Essential Phone, который не взлетел). Но что мы видим сейчас? Huawei P20, Asus Zenfone 5, OnePlus 6, Motorola One Power, Xiaomi Redmi 6 и другие более-менее известные производители уже выпускают или анонсировали телефоны с чёлкой. Samsung и Google остались последними оплотами в этой гонке за хайпом борьбе за безрамочность. Или нет? По слухам, Google Pixel 3 XL тоже будет с этой хренью с изящным вырезом. Что ж, нам, как разработчикам, остаётся только оптимизировать свои приложения под этот вырез, чтобы пользователи смогли продолжать комфортно ими пользоваться. За подробностями прошу под кат.

«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 1

Для начала нам необходимо разобраться, нужна ли вообще оптимизация приложению?
Если у вас fullscreen-приложение или в теме присутствуют windowActionBarOverlay = true, то с большой вероятностью нужна.

Практически все приложения состоят далеко не из одного экрана, и можно не заметить, как на одном из них поедет вёрстка. Особенно если в приложении объёмный legacy code. Поэтому стоит всё-таки пройтись по всем основным экранам и перепроверить. Давайте разберёмся, что для этого нужно сделать.

1. Подготовить тестовый девайс/эмулятор

Для того чтобы протестировать ваше приложение с чёлкой, нужна (спасибо, кэп!) Android P. В данный момент доступна версия Android P Preview 5 для следующих устройств (спасибо Project Treble):
Essential Phone;
Google Pixel 2;
Google Pixel 2 XL;
Google Pixel;
Google Pixel XL;
Nokia 7 plus;
OnePlus 6;
Oppo R15 Pro;
Sony Xperia XZ2;
Vivo X21UD;
Vivo X21;
Xiaomi Mi Mix 2S.

Чтобы установить Android P на устройство, достаточно перейти сюда и нажать «Получить бета-версию» для вашего устройства. Получать её по воздуху или накатывать самому — выбор за вами. Инструкция на сайте прилагается.
Но если вы не можете или не хотите устанавливать Android P на устройство, то никто не отменял эмулятор. Иструкция по настройке тут.

2. Включить саму чёлку программно (если нет аппаратной)

Тут всё просто: идём в System -> Developer options -> Simulate a display with a cutout.
Здесь на выбор предоставляются 3 варианта:

  • Corner
  • Double
  • Tall

«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 2
Выглядят они следующим образом:

Corner Double Tall
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 3 «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 4 «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 5

3. Пройтись по основным экранам

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

Explore Profile
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 6 «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 7

Теперь давайте посмотрим, какие есть способы устранения недостатков вёрстки.

Не повышая compileSdkVersion

Начиная с 20 API, появился класс WindowInsets, который представляет собой объекты Rect, описывающие доступные и недоступные части экрана. Вместе с ними во View появились такие методы, с помощью которых мы можем обрабатывать координаты недоступных частей экрана:

WindowInsets dispatchApplyWindowInsets(WindowInsets);
WindowInsets onApplyWindowInsets(WindowInsets);
void requestApplyInsets();
void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener);

Подробно о том, как ими пользоваться, тут.

Использовать эти методы можно двумя способами:
а) поставить тег android:fitsSystemWindows="true" в вёрстке на ваш layout или view;
б) сделать это из кода:

layout.setFitsSystemWindows(true);
layout.requestApplyInsets();

Было Стало
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 8 «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 9

Повысить compileSdkVersion до версии 28

В ближайшем будущем придётся переходить на эту версию, так почему бы не подготовиться к этому сейчас? Но будьте внимательны, если у вас в проекте есть юнит-тесты (а я надеюсь, они у вас есть), пакет JUnit переехал. Как его подключать, описано тут.

Итак, какие варианты теперь предоставляет нам Android P?

А. У WindowManager.LayoutParams появилось 3 новых флага:

  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT — с этим флагом чёлка будет поверх экрана приложения только в режиме portrait, в landscape же будет просто чёрная полоса;
    «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 10
    «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 11
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER — с этим флагом модной чёлки не будет вообще, она сольётся с чёрной полосой;
    «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 12
    «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 13
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES — при использовании этого флага чёлка есть всегда и в любой ориентации.
    «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 14
    «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 15

Как применять?

window.attributes.layoutInDisplayCutoutMode =
    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

Б. Если же вариант А вам не подходит и нужно учитывать именно расположение злополучного выреза (например, у вас что-то отображается прямо в статус-баре, как сообщения о соединении в Telegram), то в данном случае поможет новый класс DisplayCutout.
Рассмотрим его методы:

С ними вы сможете уже сделать всё, на что хватит фантазии. Хотите — двигайте margin в коде по ним. Хотите — обрабатывайте в OnApplyWindowInsetsListener и делайте consumeDisplayCutout(). Возможно, вам нужны более сложные манипуляции. Я приведу простой пример, как обозначить чёлку.

class SampleFragment() : Fragment() {

	private lateinit var root: ViewGroup

	override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
		return inflater.inflate(R.layout.sample_fragment, container, false)
	}

	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
		super.onViewCreated(view, savedInstanceState)
		root = view.findViewById(R.id.root)
		addArrowsToCutout()
	}

	private fun addArrowsToCutout() {
		//Нужно учитывать, что фрагмент должен успеть сделать attach к window, иначе тут будут null'ы
		val cutoutList = root.rootWindowInsets?.displayCutout?.boundingRects
		cutoutList?.forEach {
			addArrow(context!!.getDrawable(R.drawable.left), it.left.toFloat(), it.top + (it.bottom - it.top).toFloat() / 2,
			         ::calculateLeftArrow)
			addArrow(context!!.getDrawable(R.drawable.right), it.right.toFloat(), it.top + (it.bottom - it.top).toFloat() / 2,
			         ::calculateRightArrow)
			addArrow(context!!.getDrawable(R.drawable.top), it.left + (it.right - it.left).toFloat() / 2, it.top.toFloat(),
			         ::calculateTopArrow)
			addArrow(context!!.getDrawable(R.drawable.bottom), it.left + (it.right - it.left).toFloat() / 2, it.bottom.toFloat(),
			         ::calculateBottomArrow)
		}
	}

	private fun addArrow(arrowIcon: Drawable, x: Float, y: Float, calculation: (View, Float, Float) -> Unit) {
		val arrowView = ImageView(context)
		arrowView.setImageDrawable(arrowIcon)
		arrowView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
		root.addView(arrowView)
		arrowView.post {
			calculation(arrowView, x, y)
		}
	}

	private fun calculateLeftArrow(arrowView: View, x: Float, y: Float) {
		arrowView.x = x - arrowView.width
		arrowView.y = y - arrowView.height / 2
	}

	private fun calculateRightArrow(arrowView: View, x: Float, y: Float) {
		arrowView.x = x
		arrowView.y = y - arrowView.height / 2
	}

	private fun calculateTopArrow(arrowView: View, x: Float, y: Float) {
		arrowView.x = x - arrowView.width / 2
		arrowView.y = y - arrowView.height
	}

	private fun calculateBottomArrow(arrowView: View, x: Float, y: Float) {
		arrowView.x = x - arrowView.width / 2
		arrowView.y = y
	}
}

Portrait

Corner Double Tall
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 16 «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 17 «Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 18

Landscape

Corner
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 19
Double
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 20
Tall
«Поясняем за чёлку» в Android P. Что делать с Android Cutout? - 21

Итак, как мы видим, чёлка принесёт нам некоторые неудобства и заставит совершить лишние телодвижения/дополнительные манипуляции. В принципе, всё решаемо. Главное, приступить к устранению недостатков вёрстки как можно раньше, чтобы иметь в запасе достаточно времени на подготовку. Удачно вам справиться с правками. Да не сломает Google свой Play!

Автор: Дмитрий Васильев

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js