Привет, сообщество! Меня зовут Илья Бу. и в этой статье я хочу с вами поделиться болью (опытом), как нам в приложении PREMIER на ANDROID TV пришлось реализовать не совсем стандартный UI. К счастью (нет), у нас есть библиотека Leanback от Jetpack, которая призвана упростить (точно нет) разработку приложений на Android TV для разработчиков. В данной статье мы рассмотрим, как реализовать обычный экран Android на Android TV. Интересно? Тогда погнали! В мобильных приложениях мы часто пользуемся экранами, где есть категории или подкатегории. И в основном, когда такой экран необходимо отобразить, используется TabLayout (для категорий контента), и ниже отобразить — RecyclerView, в котором будет выводиться список с элементами по категории. В нашем приложении один из таких экранов выглядит следующим образом: Тут не должно возникнуть никаких сложностей, и на мобилке мы бы сделали что-то типа такого: В коде выше мы инициализируем наши вьюшки, инициализируем TabLayout, RecyclerView и в какой-то момент обновляем данные для RecyclerView.Adapter. Но что же будет в Android TV? Давайте попробуем разобраться. Для начала необходимо понять, как отображаются привычные для нас экраны в Leanback. В упрощенном виде все отображение строится на RecyclerView. Основным компонентом отображения является BrowseFragment Этот фрагмент используется для отображения контента пользователям, по которому можно перемещаться с помощью пульта (т. е. настроены механизмы изменения состояния фокуса элементов при переключении через пульт).  Если работу Leanback представить в виде схемы, то она будет выглядеть следующим образом: Разберем данную схему поподробнее. Главным элементом отображения является LeanbackFragment. Он представляет любой из фрагментов, предоставленных в Leanback.  Рассмотрим два фрагмента: BrowseSupportFragment — используется для реализации экрана с браузером каталога; DetailsSupportFragment — используется для реализации экрана с подробностями.  Каждый из фрагментов можно рассматривать как представление RecyclerView. Он отображает строки (Row), предоставляемые адаптером. Адаптер является наследником ObjectAdapter, который зависит от подкласса Presenter. Он преобразует элементы адаптера в экземпляры View, отображаемые во фрагменте. Элементы ObjectAdapter могут быть любого типа, если есть реализация Presenter, которая умеет преобразовывать этот тип в вид. Существует два типа презентаторов: RowPresenter отображает объекты в виде строк; ListRowPresenter визуализирует особый тип объектов Row, содержащих заголовок и список. Рассмотрим VerticalGridFragment. Он позволяет отобразить список как с горизонтальными Rows, так и с вертикальными Grid Rows. В базовом виде данный фрагмент выглядит следующим образом: Код для отображения будет выглядеть следующим образом: Здесь происходит инициализация PagingDataAdapter и VerticalGridPresenter для отображения списка элементов в виде Grid. Чтобы изменить titleView, необходимо переопределить метод onInflateTitleView: В этом случае, если R.layout.title_view включает в себя макеты, помимо тех, которые предлагает стандартная реализация Leanback, мы получим: Проблемы с переключением фокусов между title и list; Проблемы с анимацией скрытия/отображения title при скролле list вниз/вверх; Проблемы с transition для list. Выглядит это следующим образом: Вторую проблему можно побороть в лоб — скрывать title без анимации: Третья проблема решается путем ручного расчета transaction (или высоты titleView) и установки transaction для list: Результат будет выглядеть следующим образом: А вот при попытке решения первой проблемы возникают сложности. Одной из проблем будет слет фокуса между title и list. И тут нам необходимо разобраться, каким образом переключается фокус между Right Menu, TitleView и GridView. Главный layout, по которому перемещается BrowseFrameLayout. Данный layout содержит внутри себя все элементы интерфейса, и все переключения фокуса происходят внутри него. Мы можем установить onFocusSearchListener и определять, какой из View должен получить следующий фокус в зависимости от предыдущего View и направления, куда движется фокус. Чтобы решить вопрос с фокусом между title и list, необходимо также установить onFocusSearchListener для BrowseFrameLayout. Данный код иллюстрирует механизм установки фокуса для title + list в зависимости от предыдущего состояния фокуса и направления, куда требуется переместить фокус. Например, при переключении фокуса вниз (direction = View.FOCUS_DOWN) и если фокус был установлен на title, то нам необходимо установить фокус на list. Если же direction = View.FOCUS_UP и при это фокус находится на title, то необходимо переключиться на orbView (view с поиском для TV). Подытожив, мы получаем ситуацию, когда при сложных и нестандартных макетах (отличающихся от google-guidelines) нам необходимо самим писать обработчики фокусов для всех элементов TV, что не всегда понятно и приятно. И при разработке под Android TV всегда необходимо учитывать риски на изучения механизмов Leanback, либо погружение в механизм управления фокусов в Android для понимания, как реализовать задуманное поведение.  Программист