В этом видео разберем, как обрабатывать нажатия на элементы списка. Меньше слов - больше дела. Давайте начнем. Во-первых, перейдем в наш адаптер, ContactsAdapter, и определим там интерфейс. Public, interface, onItemClick listener. В этом интерфейсе зададим метод, void, onItemСlic;. Ну, логика какая, при нажатии на элемент списка вызывается метод onItemClick, который определен в этом интерфейсе. Получается, во-первых, этот интерфейс какая-то часть нашего кода какой-то элемент, какой-то компонент должен во-первых, реализовывать, во-вторых, этот интерфейс, реализация этого интерфейса должна быть передана в holder, чтобы именно конкретно нажатый элемент мог быть обработан в месте её реализации. Давайте сначала определимся, кто будет реализовывать этот интерфейс. Вообще желательно, чтобы реализацией занималась именно activity, потому что чем выше реализация, тем лучше, потому что реализовав интерфейс в activity, я могу менять логику впоследствии не затрагивая остальные элементы. Ну примерно, например, я могу нажать на кнопку и запустить звонок. Нажать на элемент списка, запустить звонок. Либо я могу нажать на элемент списка и поменять один фрагмент на другой. Если реализация интерфейса будет ниже во фрагменте, либо в адаптере, либо в холдере, то при изменении логики мне придётся поменять очень много зависимых от неё от этой логики классов, поэтому реализацией интерфейса займётся activity. Я надеюсь, я понятно объяснил почему. implenents, ContocsAdapter, onClickListener, Alt+Enter, implements_metods, onItemClick. Хорошо. Пока что реализация ничего не будет делать, подумаю над тем, как передать реализацию в холдер. Перейдем в RecyclerFragment. RecyclerFragment, да и первым делом удалим вот весь этот код, который остался с прошлого раза. RecyclerFragment является своего рода прослойкой между activity и адаптером, так же, как и в прошлом курсе, мы разбирали как передавать данные и логику из фрагментов в activity, ну посредством каста, в методах onAttach и зануление в методе onDetach точно так же создадим поле с Lisner-ом и будем инициализировать его в методе onAttach. Privote, OnClickListener, Listener;, onAttach, if context InstanceOf, onClickListener, то сохраняем Listener равен context, Alt+Enter, Alt+Enter, каст. В onAttachе мы записали значения в поле, соответственно в методе onDetach мы это поле зануляем. mListener = null но, чтобы не мешать сборщику мусора. Дальше Listener теперь передан из activity во фрагмент. Нам нужно дальше, по цепочке передать его в адаптер. ContextAdopter.setListener и передаем наш Listener. Щелкаем по выделенному методу Alt+Enter и выбираем Create Setter. Create Setter, помимо самого метода, создаст еще и поле - Listener. Одна из фишечек InteljID, либо AndroidStudio на базе InteljID как раз студия и была построена. Так, теперь Listener передан в адаптер, теперь нам нужно передать его дальше, в холдер. Как мы можем это сделать? Ну мы можем, во-первых, переопределить один из методов, точнее просто переопределить метод bind, либо задать его отдельным методом. Так, второй способ, setListener, mListener, Alt+Enter, Create. В этом случае мы выбираем Create Method, потому что я не хочу, чтобы в холдере, нажимаем Tab, я не хочу, чтобы в холдере хранилась ссылка на Listener. Что мы делаем дальше? Будучи в фолдере, мы можем обратиться к самому элементу списка, ну не конкретно к её части, например, TextView, mVolue или mName, а ко всему элементу целиком. Чтобы это сделать, нам нужно просто вызвать поле ItemView, если посмотреть на конструктор холдера, то видно, что оно задавалось в конструкторе. Итак, mItemView и точно так же как и с любым View элементом, задаем ему onClickListener, стандартный. setItemClickListener, new, Alt+Enter, почему говорю Alt+Enter? Ctrl+пробел, Listener, onClick, и в методе onClick пишем, что Listener. onItemClick, то есть при нажатии на элемент списка вызывается метод onItemClick переданного onItemClickListener-а. Так, давайте перейдем в MainActivity и зададим какую-нибудь логику. Tosst, Ctrl+пробел, context - this, текст, не знаю, пускай будет Clicked. Запускаем. Обновили список, щелкнули на элемент из списка, видим, показывается Tosst. Хорошо. Теперь, что нам нужно сделать? Нам нужно передать ID щелкнутого элемента, чтобы я мог отличить, что щелкнули либо на первый элемент, либо на четвертый, либо на шестой. Сейчас это не сделано. Переходим к определению нашего интерфейса, наводимся на метод, нажимаем Shift+F6 и меняем сигнатуру. Вообще я думал, что сработает автозамена, но по ходу не сработало. Да ладно, переходим MockHolder и видим, что onItemClick теперь должен получать на вход ID. ID мы можем получить либо из поля textView mVolue, либо можем сохранить его в отдельное поле. Пускай будет Private String mId; и mId = Mock.getValue. Соответственно, в onItemClick передаем mID Так, RecyclerFragment у нас без эффектов, а вот в MainActivity нужно добавить аргумент в метод onItemClick и попробовать проверить, как она работает теперь. Добавляем в сообщении, которое должно увидится при Tosst-е, переменная текст, айдишник, который мы, получается, передали из холдера. Щелкаем Run, обновляем, щелкаем на первый элемент, видим Clicked 1, щелкаем на второе, Clicked 2, щелкаем на шестой, видим Clicked 6, но то есть всё работает как надо. Возвращаемся к нашей activity. Так, теперь добавим какую-нибудь логику обработки нажатия на элемент списка, который отличается от просто показа Tosstа. Давайте сделаем так, чтобы при нажатии на имя контакта, то есть на элемент списка с контактом, у нас запускался звонок этому контакту. Первая проблема - у нас нет номера телефона. Дело в том, что контент провайдер, который мы использовали в прошлом уроке, не содержит номера телефонов, чтобы получить номер телефона, контент провайдер с номером телефона, нам нужно обратиться по другому контент юрай, и в этом видео для сравнения я не буду добавлять louder-ы, а просто обращусь к контент провайдеру напрямую. Как это сделать? Я просто пишу getContentResolver и получаю контент провайдер. Точка, вызываем метод querty. Если скрытая сигнатура метода, то она кажется подозрительно знакомая. Ну, не удивительно на самом деле, потому что query возвращает курсор и создание курсор louder-a похоже на как раз таки, на запуск метода query. Тот же юрай, тот же проджекшн, селекшн, аргументы и сорт ордер. Я думаю, это понятно почему. Так, давайте заполнять. Во-первых, контент юрай, ContactsContract, точка. В этот раз заходим в CommandDataKinds. фон и контент юрай, запятая. Первый аргумент у нас есть. Дальше, проджекшн, то есть какие столбцы мне нужны. Мне нужен на самом деле только один столбец, это столбец с номерами телефонов, но мне все равно нужно обернуть этот столбец в массив, потому что вызов метода, аргумент метода это требует. Поэтому я пишу new string и создаю просто массив из одного элемента. ContactsContract. точка CommandDataKinds., точка фон и выбираю Number. Дальше, селекшн, мне не нужны все номера из этой таблицы, мне нужен только номер конкретно того человека, на который я щелкнул, и для этого мне нужно сравнить ID из текущей таблицы, из текущего контент провайдера с тем айди, который пришел ко мне на вход метод onItemClick. Ну что же, давайте напишем это, да. ContactsContract., CommandDataKinds. [inaudible] , точка Phone и видим Contact ID равен, открываем кавычки, не равен на самом деле, Contact ID плюс открываем кавычки пробел равно вопросительный знак. Место вопросительного знака, при запуске этого метода, подставится элемент из следующего аргумента, из selection arcs. Да, еще, мне нужен не абы какой номер, мне нужен именно мобильный телефон, поэтому я добавлю ещё один атрибут для сравнения в строку selection. Пишу дальше енд, плюс ContractCommands. точка CommandDataKinds [inaudible] , Phone и ContentType. Нет, вру, просто тайп, плюс кавычки, так, плюс кавычки равно вопросительный знак. С selectiom-ом определились. Теперь selectionArcs, т.е. значение, которое потом поставятся в место вопросительного знака, в строку selection. Это тоже массив, поэтому пишем new string и передаем. Первым элементам будет ID, которые мы получили на входе, а второй элемент, это тип мобайл, который можно найти в том же CommandDataKinds. точка Phone. Type Mobile. Type Mobile - это на самом деле число, нужно костонуть его к строке. Пишем string, ValueOff, запятая, и последним, так, так, так, так, последним аргументом в этот метод является sort order. Ну я полагаю, что это мне сейчас вообще неважно, поэтому я передаю нал. Так, метод готов, точка запятая в конце. Так, что мы имеем в конечном итоге? Мы создали контент провайдер, обращаемся к нему, получаем курсор, который будет хранить один столбец, который называется number, то есть номер телефона, в котором мы сравниваем ID, которые находятся в этом столбце с ID, которое мы ему передали и сравниваем тип номер телефона с тайп мобайл, то есть с мобильным номером. Звучит сложно, выглядит еще сложнее, но я думаю вы разберетесь. Да, кстати, куери возвращает нам курсор, давайте загоним его в переменную. Наводимся на куери, CommandAltV, либо CntrlAltV, если вы работаете на Виндоус. Получили переменную, дальше, почему куери, давайте назовем его курсор. Дальше проверяем, иф курсор не нал, то давайте парсить, что там внутри находится. Кстати, у курсора есть метод MoveToFirst, который работает как MoveToPosition, ну только переносят его к первому элементу таблицы. Я полагаю, что у меня будет только один номер такого типа, поэтому я перемещаясь к самому началу, string, number равен курсор, как делали в прошлом уроке, GetString,Cursor,GetCollumnIndeks, context контракт, точка [inaudible] точка фон, точка с запятой, получили номер, закрыли курсор, и теперь нам нужно создать интент для запуска приложения для звонков. Start Activity, передаем туда NewIntent, Action, Action Call, полагаю, точка сет дата, юэрай, парс, тел плюс намбр, точка с запятой. В принципе этого кода должно быть достаточно для запуска приложения с полученным номером телефона. На самом деле нет, экшен коул требует пермишна, так же как и рид контактс, так, запрашиваем пермищн и скорее всего приложение сейчас снова обвалится, потому что я не проверяю, что у меня есть этот пермищн, но ничего, мы воспользуемся лайфхаком, и запросим этот пермищн прямо в настройках. Рецайклер тест, пермищнс, подрубаем пермищн на совершение звонков, возвращаемся, переключаемся на рецайклер, обновили, щелкаем по первому элементу и запускается приложение для совершения звонков. Итак, что мы сделали? Чего мы добились в этом уроке? Мы передали данные из холдера в activity путём реализации интерфейса, который определен в адапторе и передачи Listenerа из activity в фрагмент, из фрагмента в адаптер, из адаптера в холдер. Прямая связь и получается вызов этого интерфейса приводит к тому, что срабатывает код в майн activity, где этот интерфейс реализован, это получается обратная связь. Дальше, в методе onItemClick реализован интерфейса, мы обращаемся к контент провайдеру, проводим запрос в базу данных, ищем номер телефона для контакта, айди которого мы передали этот метод, следим, чтобы номер телефона был мобильный, дальше проверяем, что полученный нами курсор, он не пустой и в нём есть данные, выдергиваю оттуда номер телефона, создаем не явный интент для совершения звонков, указываем в нём данные номер телефона, по которому мы хотим позвонить и запускаем activity. Это очень много. Дальше, в чём проблема этого кода? Ну, несмотря наесли не акцентироваться на том, что я не проверяю пермищн в рантайме, проблема в том, что я обращаюсь к контент провайдеру, делаю запрос в базу, проверяю, выдергиваю данные, закрываю курсор, вот все, все, все это, я делаю это в мейн трейде. Это на самом деле неправильно. По хорошему я должен был обратиться к контент провайдеру в фоновом потоке, провести все эти манипуляции, и когда всё будет готово, просто возвращать номер телефона в главный поток, с которого я уже могу позвонить, и это всё из-за того, что я отказался использовать лоудеры. В принципе это можно сейчас исправить, создать сервис екзикютер, например, выполнить в нём эту таску, получить фьючер, из фьючер выдернуть строку с номером и передать её в старт activity. Но я не буду этого делать, потому что это будет одним из ваших тестовых заданий. Так, обработку нажатий мы тоже реализовали, в следующем уроке уроке мы поговорим о декораторах. Что это такое, с чем его едят и вообще, зачем это нужно? До встречи.