Евгений Колесников 3 anos atrás
pai
commit
6741f43c38

+ 110 - 1
articles/android_tv.md

@@ -1,5 +1,18 @@
 # Android TV
 
+* [Коротко о библиотеке Leanback](#коротко-о-библиотеке-leanback)
+* [Создание проекта в Android Studio (и разбор структуры проекта)](#создание-проекта-в-android-studio)
+    - [Манифест](#манифест)
+    - [MainActivity](#mainactivity)
+    - [MainFragment](#mainfragment)
+* [Модификация под свои нужды](#модификация-под-свои-нужды)
+    - [Загрузочный экран](#загрузочный-экран)
+    - [Получение списка фильмов с сервера](#получение-списка-фильмов-с-сервера)
+    - [Группировка по категориям](#группировка-по-категориям)
+    - [Настройка вёрстки карточки фильма](#настройка-вёрстки-карточки-фильма)
+    - [Реализация самописанной (custom) разметки карточки](#реализация-самописанной-custom-разметки-карточки)
+    - [Разбор оставшихся методов главного фрагмента](#разбор-оставшихся-методов-главного-фрагмента)
+
 >Содрано [отсюда](https://habr.com/ru/post/316260/), [отсюда](https://skillbox.ru/media/code/razrabotka_pod_android_tv/) и [отсюда](https://skillbox.ru/media/code/razrabotka_pod_android_tv_part2/)
 
 [Первая](https://habr.com/ru/post/316260/) статья довольно древняя (2016 год), попробуем реализовать в 2022
@@ -704,7 +717,7 @@ private inner class GridItemPresenter : Presenter() {
 
 Сделаем свою карточку: **CardView** со скруглёнными углами, картинка, текст и переключатель (**Switch**) с функцией обратного вызова:
 
-1. Нарисуем разметку в `layout/card_item.xml`
+1. Нарисуем разметку в `layout/card_item.xml` (мой пример не копипастить - у каждого должна быть своя реализация)
 
     ```xml
     <?xml version="1.0" encoding="utf-8"?>
@@ -844,6 +857,102 @@ val bundle = ActivityOptionsCompat
     )
 ```
 
+### Фон главного окна
+
+Фон автоматически устанавливается при выборе карточки фильма. Но у экземпляра фильма при этом должно быть установлено свойство *backgroundImageUrl*, которое мы пока не заполняли. 
+
+В АПИ в списке изображений (images) я добавил по картинке для каждого фильма. 
+
+Ваша задача при получении списка фильмов вытащить элемент этого массива и присвоить его свойству *backgroundImageUrl*.
+
+### Разбор оставшихся методов главного фрагмента
+
+1. *setupUIElements* - настройка брендирования
+
+    * Установка названия бренда (строка в правом верхнем углу):
+
+        ```kt
+        title = getString(R.string.browse_title)
+        ```
+
+        Вместо текста можно вывести логотип:
+
+        ```kt
+        badgeDrawable = ContextCompat.getDrawable(
+            context, 
+            R.drawable.app_icon_your_company)
+        ```
+
+        Причём установить можно только один из этих элементов, и наибольший приоритет всегда у логотипа.
+
+    * Настройка режима отображения левой панели (список категорий).
+
+        В базовой реализации **HeadersFragment** сразу виден пользователю. Это поведение можно изменить с помощью метода *setHeadersState* (в Котлине просто сеттер *headersState*). К нему нужно обратиться во время настройки и передать одно из состояний:
+
+        * HEADERS_ENABLED — фрагмент виден пользователю.
+        * HEADERS_HIDDEN — фрагмент свёрнут.
+        * HEADERS_DISABLED — фрагмент полностью скрыт с экрана.
+
+        ```kt
+        headersState = BrowseSupportFragment.HEADERS_ENABLED
+        isHeadersTransitionOnBackEnabled = true
+        ```
+
+    * Цвет левой панели
+
+        ```kt
+        brandColor = ContextCompat.getColor(
+            activity!!, 
+            R.color.fastlane_background)
+        ```
+
+    * Настройка иконки "поиска" (напомню, сама иконка появляется, если для неё задан обработчик)
+
+        ```kt
+        searchAffordanceColor = ContextCompat.getColor(
+            activity!!, 
+            R.color.search_opaque)
+        ```
+
+1. *setupEventListeners*
+
+    * Включение иконки поиска
+
+        Как уже выше писалось, иконка автоматически включается, если для неё задан обработчик. В примере ничего не делается, просто выводится сообщение на экран
+
+        ```kt
+        setOnSearchClickedListener {
+            Toast.makeText(
+                activity!!, 
+                "Implement your own in-app search", 
+                Toast.LENGTH_LONG)
+            .show()
+        }
+        ```
+
+    * Назначение событий при клике и активации карточки фильма
+
+        ```kt
+        onItemViewClickedListener = ItemViewClickedListener()
+        onItemViewSelectedListener = ItemViewSelectedListener()
+        ```    
+
+1. *ItemViewClickedListener*
+
+    Обработчик клика по карточке фильма. При клике происходит переход на окно детальной информации о фильме (с анимацией)
+
+1. *ItemViewSelectedListener*
+
+    Обработчик события выбора карточки фильма (при перемещении по списку фильмов)
+
+    В стандартной реализации устанавливается фон окна (с анимацией), но в итоге интерес представляет только эта строка:
+
+    ```kt
+    mBackgroundManager.drawable = drawable
+    ```
+
+<!-- https://medium.com/@Marcus_fNk/building-an-android-tv-app-part-2-824766c1ddbe -->
+
 
 <!-- https://tv.withgoogle.com/# -->
 

BIN
cinema/images/big_wp.jpg


BIN
cinema/images/duna_wallpaper.jpg


BIN
cinema/images/green_wp.jpg


BIN
cinema/images/house_wp.webp


BIN
cinema/images/liber_wp.jpg


BIN
cinema/images/main_hero_wp.jpg


BIN
cinema/images/papa2.jpg


BIN
cinema/images/paris_wp.webp


BIN
cinema/images/parma_wp.jpg


BIN
cinema/images/piter_1_wp.webp


+ 32 - 10
cinema/index.js

@@ -33,16 +33,38 @@ app.use((req, res, next)=>{
 
 const registeredUsers = []
 const movies = [
-  {movieId: 1, category:'Фентези', name: 'Дюна', description: 'Атрейдесы прибывают на планету, где им никто не рад. Фантастический эпос Дени Вильнёва с шестью «Оскарами»', age: "12", images: [], poster: 'duna.webp', tags: [], filters: ['new','inTrend','forMe']},
-  {movieId: 2, category:'Фентези', name: 'Легенда о Зелёном Рыцаре', description: 'Наследник короля принимает вызов таинственного рыцаря. Захватывающее фэнтези по мотивам средневековой поэмы', age: "18", images: [], poster: 'green.webp', tags: [], filters: ['new','inTrend','forMe']},
-  {movieId: 3, category:'Мелодрама', name: 'Главный герой', age: "16", images: [], poster: 'maincharacter.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'Парень по имени Парень счастлив. Он живет в лучшем в мире городе Городе, работает на лучшей в мире работе в Банке и дружит с охранником по имени Приятель. И его совершенно не волнует, что Банк грабят по нескольку раз на дню, а улицы Города напоминают зону военных действий. Единственное, чего Парню не хватает для полного счастья — идеальной девушки, к которой у него имеется точный список требований. И вот однажды он видит на улице красотку, точь-в-точь как в его мечтах. Эта встреча изменит не только нашего главного героя, но и перевернёт весь известный ему мир.'},
-  {movieId: 4, category:'Исторический', name: 'Петр I: Последний царь и первый император', age: "12", images: [], poster: 'Petr1.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'Фигура императора Петра Великого, как и эпоха его становления и правления, до сих пор будоражит умы людей во всем мире. Создатели отвечают на вопросы, как занять престол, когда ты — четырнадцатый ребенок в семье; как отвоевать выход к морю, когда в стране нет профессиональной армии и флота; как за несколько десятилетий вывести в мировые лидеры страну, с которой раньше никто не считался и многие другие.'},
-  {movieId: 5, category:'Приключения', name: 'Либерея: Охотники за сокровищами', age: "12", images: [], poster: 'Liberia.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'При строительстве столичного метро рабочие обнаруживают драгоценный оклад, который доказывает — легендарная Библиотека Ивана Грозного существует! Но находка оказывается забыта на долгие годы, и уже в наше время попадает в руки ни о чем не подозревающего Ильи. Теперь его жизнь в опасности, ведь за старинным артефактом начинают охоту могущественные силы! Парень вынужден объединиться со странным незнакомцем, который утверждает, что оклад — это ключ к обнаружению Библиотеки. Помочь им в поисках и разгадать древние шифры берется красотка-филолог Арина. Теперь, чтобы обрести новые ключи-подсказки и приблизиться к разгадке, трио авантюристов нужно побывать в затерянных и опасных местах, разбросанных по всей России: от Вологды до Нарьян-Мара, на суше, под водой и даже в тайных подземельях Кремля.'},
-  {movieId: 6, category:'Исторический', name: 'Грозный папа', age: '6', images: [], poster: 'formidableDad.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'Поссорившись с сыном, царь Иван Грозный случайно ранит его – как на знаменитой картине Репина. Жизнь царевича на волоске. Чтобы все исправить, Грозный хочет отправиться в прошлое с помощью волшебного гримуара. Однако что-то пошло не так, и Грозный попадает в наше время, где знакомится с семьей Осиповых. Никита Осипов – неудачливый археолог и такой же неудачливый отец. Он давно потерял контакт с детьми – Ромкой и Полей. Но теперь они вместе отправляются в путешествие, чтобы помочь Грозному отыскать гримуар и спасти царевича.'},
-  {movieId: 7, category:'Мелодрама', name: 'Сердце пармы', age: '16', images: [], poster: 'Heart.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'Русский князь Михаил и юная Тиче — дети разных народов, разных миров и разных богов. Любовь молодого воителя и ведьмы-ламии кажется невозможной, но преодолевает все запреты, запуская маховик рока. Отныне только от Михаила зависит будущее родной пармы, древних суровых земель, напоенных чудодейственной мощью кровавых языческих богов. Здесь сталкиваются герои и призраки, князья и шаманы, вогулы и московиты. Здесь расстаться с жизнью — не так страшно, как выбрать между долгом, верностью братству и любовью к единственной женщине на свете.'},
-  {movieId: 8, category:'Мультики', name: 'Большое путешествие. Специальная доставка', age: '6', images: [], poster: 'bigAdventure.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'Прошло время с тех пор, как заяц Оскар и медведь Мик-Мик в компании своих друзей вернули домой маленького панду. С тех пор жили они спокойно и размеренно. Мик-Мик заботился о своих пчелах, а Оскар организовал в лесу американские горки. И вот однажды к берегу Мик-Мика прибивает корзину с малышом гризли. Кто-то снова перепутал адреса, а разбираться с этим придется Мик-Мику и Оскару. В компании друзей они отправляются в новое путешествие — теперь, чтобы вернуть домой малыша гризли.'},
-  {movieId: 9, category:'Мелодрама', name: 'Шрамы Парижа', age: '18', images: [], poster: 'scars.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'В ноябре 2015 года Париж пережил самые страшные теракты в своей истории. Жертвами тщательно спланированных актов насилия стали почти 400 человек. Но на этом преступники не собирались останавливаться. Чтобы предотвратить будущие угрозы, двум агентам придется провести одно из самых крупных расследований в истории Старого Света и помешать преступникам нанести новый удар. Теперь в опасности не только Франция, но и вся Европа.'},
-  {movieId: 10, category:'Ужасы', name: 'Паранормальные явления. Дом призраков', age: '16', images: [], poster: 'Paranormal.webp', tags: [], filters: ['new','inTrend','forMe'], description: 'Когда-то Шон был популярным видеоблогером, сделавшим имя на экстремальных роликах, в которых он бросал вызов собственным страхам, но однажды вляпался в скандал и потерял всех спонсоров. Записав видео с извинениями и снова получив финансирование, парень возвращается с новым леденящим душу проектом. Шон собирается провести ночной стрим из дома с привидениями, где более 100 лет назад повесилась одинокая женщина, а после неоднократно фиксировалась паранормальная активность.'}
+  {movieId: 1, category:'Фентези', name: 'Дюна', 
+    description: 'Атрейдесы прибывают на планету, где им никто не рад. Фантастический эпос Дени Вильнёва с шестью «Оскарами»', 
+    age: "12", images: ['duna_wallpaper.jpg'], poster: 'duna.webp', tags: [], 
+    filters: ['new','inTrend','forMe']},
+  {movieId: 2, category:'Фентези', name: 'Легенда о Зелёном Рыцаре', 
+    description: 'Наследник короля принимает вызов таинственного рыцаря. Захватывающее фэнтези по мотивам средневековой поэмы', 
+    age: "18", images: ['green_wp.jpg'], poster: 'green.webp', tags: [], 
+    filters: ['new','inTrend','forMe']},
+  {movieId: 3, category:'Мелодрама', name: 'Главный герой', age: "16", 
+    images: ['main_hero_wp.jpg'], poster: 'maincharacter.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'Парень по имени Парень счастлив. Он живет в лучшем в мире городе Городе, работает на лучшей в мире работе в Банке и дружит с охранником по имени Приятель. И его совершенно не волнует, что Банк грабят по нескольку раз на дню, а улицы Города напоминают зону военных действий. Единственное, чего Парню не хватает для полного счастья — идеальной девушки, к которой у него имеется точный список требований. И вот однажды он видит на улице красотку, точь-в-точь как в его мечтах. Эта встреча изменит не только нашего главного героя, но и перевернёт весь известный ему мир.'},
+  {movieId: 4, category:'Исторический', name: 'Петр I: Последний царь и первый император', 
+    age: "12", images: ['piter_1_wp.webp'], poster: 'Petr1.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'Фигура императора Петра Великого, как и эпоха его становления и правления, до сих пор будоражит умы людей во всем мире. Создатели отвечают на вопросы, как занять престол, когда ты — четырнадцатый ребенок в семье; как отвоевать выход к морю, когда в стране нет профессиональной армии и флота; как за несколько десятилетий вывести в мировые лидеры страну, с которой раньше никто не считался и многие другие.'},
+  {movieId: 5, category:'Приключения', name: 'Либерея: Охотники за сокровищами', 
+    age: "12", images: ['liber_wp.jpg'], poster: 'Liberia.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'При строительстве столичного метро рабочие обнаруживают драгоценный оклад, который доказывает — легендарная Библиотека Ивана Грозного существует! Но находка оказывается забыта на долгие годы, и уже в наше время попадает в руки ни о чем не подозревающего Ильи. Теперь его жизнь в опасности, ведь за старинным артефактом начинают охоту могущественные силы! Парень вынужден объединиться со странным незнакомцем, который утверждает, что оклад — это ключ к обнаружению Библиотеки. Помочь им в поисках и разгадать древние шифры берется красотка-филолог Арина. Теперь, чтобы обрести новые ключи-подсказки и приблизиться к разгадке, трио авантюристов нужно побывать в затерянных и опасных местах, разбросанных по всей России: от Вологды до Нарьян-Мара, на суше, под водой и даже в тайных подземельях Кремля.'},
+  {movieId: 6, category:'Исторический', name: 'Грозный папа', age: '6', 
+    images: ['papa2.jpg'], poster: 'formidableDad.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'Поссорившись с сыном, царь Иван Грозный случайно ранит его – как на знаменитой картине Репина. Жизнь царевича на волоске. Чтобы все исправить, Грозный хочет отправиться в прошлое с помощью волшебного гримуара. Однако что-то пошло не так, и Грозный попадает в наше время, где знакомится с семьей Осиповых. Никита Осипов – неудачливый археолог и такой же неудачливый отец. Он давно потерял контакт с детьми – Ромкой и Полей. Но теперь они вместе отправляются в путешествие, чтобы помочь Грозному отыскать гримуар и спасти царевича.'},
+  {movieId: 7, category:'Мелодрама', name: 'Сердце пармы', age: '16', 
+    images: ['parma_wp.jpg'], poster: 'Heart.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'Русский князь Михаил и юная Тиче — дети разных народов, разных миров и разных богов. Любовь молодого воителя и ведьмы-ламии кажется невозможной, но преодолевает все запреты, запуская маховик рока. Отныне только от Михаила зависит будущее родной пармы, древних суровых земель, напоенных чудодейственной мощью кровавых языческих богов. Здесь сталкиваются герои и призраки, князья и шаманы, вогулы и московиты. Здесь расстаться с жизнью — не так страшно, как выбрать между долгом, верностью братству и любовью к единственной женщине на свете.'},
+  {movieId: 8, category:'Мультики', name: 'Большое путешествие. Специальная доставка', 
+    age: '6', images: ['big_wp.jpg'], poster: 'bigAdventure.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'Прошло время с тех пор, как заяц Оскар и медведь Мик-Мик в компании своих друзей вернули домой маленького панду. С тех пор жили они спокойно и размеренно. Мик-Мик заботился о своих пчелах, а Оскар организовал в лесу американские горки. И вот однажды к берегу Мик-Мика прибивает корзину с малышом гризли. Кто-то снова перепутал адреса, а разбираться с этим придется Мик-Мику и Оскару. В компании друзей они отправляются в новое путешествие — теперь, чтобы вернуть домой малыша гризли.'},
+  {movieId: 9, category:'Мелодрама', name: 'Шрамы Парижа', age: '18', 
+    images: ['paris_wp.webp'], poster: 'scars.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'В ноябре 2015 года Париж пережил самые страшные теракты в своей истории. Жертвами тщательно спланированных актов насилия стали почти 400 человек. Но на этом преступники не собирались останавливаться. Чтобы предотвратить будущие угрозы, двум агентам придется провести одно из самых крупных расследований в истории Старого Света и помешать преступникам нанести новый удар. Теперь в опасности не только Франция, но и вся Европа.'},
+  {movieId: 10, category:'Ужасы', name: 'Паранормальные явления. Дом призраков', 
+    age: '16', images: ['house_wp.webp'], poster: 'Paranormal.webp', tags: [], 
+    filters: ['new','inTrend','forMe'], description: 'Когда-то Шон был популярным видеоблогером, сделавшим имя на экстремальных роликах, в которых он бросал вызов собственным страхам, но однажды вляпался в скандал и потерял всех спонсоров. Записав видео с извинениями и снова получив финансирование, парень возвращается с новым леденящим душу проектом. Шон собирается провести ночной стрим из дома с привидениями, где более 100 лет назад повесилась одинокая женщина, а после неоднократно фиксировалась паранормальная активность.'}
 ]
 
 const chats = [