Для начала нам необходимо разобраться, нужна ли вообще оптимизация приложению?
Если у вас 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
Выглядят они следующим образом:
Corner | Double | Tall |
---|---|---|
![]() |
![]() |
![]() |
3. Пройтись по основным экранам
Само собой, этот кейс у всех будет разный. У кого-то простая логика, у кого-то не очень. Приведу пару примеров экранов с поехавшей вёрсткой, которые я нашёл в нашем приложении.
Explore | Profile |
---|---|
![]() |
![]() |
Теперь давайте посмотрим, какие есть способы устранения недостатков вёрстки.
Не повышая 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();
Было | Стало |
---|---|
![]() |
![]() |
Повысить compileSdkVersion до версии 28
В ближайшем будущем придётся переходить на эту версию, так почему бы не подготовиться к этому сейчас? Но будьте внимательны, если у вас в проекте есть юнит-тесты (а я надеюсь, они у вас есть), пакет JUnit переехал. Как его подключать, описано тут.
Итак, какие варианты теперь предоставляет нам Android P?
А. У WindowManager.LayoutParams появилось 3 новых флага:
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT — с этим флагом чёлка будет поверх экрана приложения только в режиме portrait, в landscape же будет просто чёрная полоса;
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER — с этим флагом модной чёлки не будет вообще, она сольётся с чёрной полосой;
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES — при использовании этого флага чёлка есть всегда и в любой ориентации.
Как применять?
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
Б. Если же вариант А вам не подходит и нужно учитывать именно расположение злополучного выреза (например, у вас что-то отображается прямо в статус-баре, как сообщения о соединении в Telegram), то в данном случае поможет новый класс DisplayCutout.
Рассмотрим его методы:
- getBoundingRects() возвращает List объектов Rect, каждый из которых обозначает недоступную область экрана;
- getSafeInsetLeft(), getSafeInsetRight(), getSafeInsetTop(), getSafeInsetBottom() возвращают левый, правый, верхний и нижний отступ без выреза в пикселях соответственно.
С ними вы сможете уже сделать всё, на что хватит фантазии. Хотите — двигайте 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 |
---|---|---|
![]() |
![]() |
![]() |
Landscape
Corner |
---|
![]() |
Double |
![]() |
Tall |
![]() |
Итак, как мы видим, чёлка принесёт нам некоторые неудобства и заставит совершить лишние телодвижения/дополнительные манипуляции. В принципе, всё решаемо. Главное, приступить к устранению недостатков вёрстки как можно раньше, чтобы иметь в запасе достаточно времени на подготовку. Удачно вам справиться с правками. Да не сломает Google свой Play!
ссылка на оригинал статьи https://habr.com/company/funcorp/blog/419109/
Добавить комментарий