Адаптеры
В Android часто используются адаптеры. Если говорить в общих чертах, то адаптеры упрощают связывание данных с элементом управления. Адаптеры используются при работе с виджетами, которые дополняют android.widget.AdapterView: ListView, ExpandableListView, GridView, Spinner, Gallery, а также в активности ListActivity и др. Сам AdapterView дополняет android.widget.ViewGroup.
Итак, у нас есть набор объектов и есть компонент View. Назначение адаптера заключается в том, чтобы предоставлять дочерние виды для контейнера. Адаптер берет данные и метаданные определенного контейнера и строит каждый дочерний вид. Например, мы формируем пункты списка (массив строк) и передаём его списку ListView.
Вы что-нибудь поняли? Мой кот тоже ничего не понял. Попробуем объяснить по-другому. Что такое вообще адаптер? Это переходник между двумя какими-то предметами. Допустим, между питьевой водой и котом требуется адаптер в виде крана.
В данном случае адаптер спроектирован плохо, приходится изворачиваться.
Однако вернёмся к Android. В приложениях очень часто используется список на основе ListView. Сам список состоит из множества элементов TextView, которые идут друг за другом. Но их количество будет зависеть от того, чтобы мы хотим отобразить. Если нам нужны дни недели, то достаточно семи элементов, если месяцы, то уже двенадцать, ну а если нам нужны имена котов в Кот д’Ивуаре, то счёт пойдет на сотни. Короче говоря, нам нужно составить данные, например, в виде массива и скормить его списку. Адаптер этим и занимается. Он берёт по порядку предоставленные данные и размещает их в списке по порядку. При этом адаптер на лету создаёт нужные компоненты TextView и помещает в него приготовленный текст. Данные могут быть находиться не только в массиве, но и в базе данных. Для такого случая используется другой адаптер. А также вы можете придумать свой адаптер. Существуют уже готовые адаптеры на самые распространённые случаи и их предназначение можно определить по именам. Например, ArrayAdapter использует массив, а CursorAdapter работает с объектом Cursor, используемый в базах данных.
Готовые адаптеры
Все адаптеры, содержащиеся в Android, дополняют базовый адаптер BaseAdapter. Вот список готовых адаптеров:
- ArrayAdapter — предназначен для работы с ListView. Данные представлены в виде массива, которые размещаются в отдельных элементах TextView
- ListAdapter — адаптер между ListView и данными. Строго говоря, это класс-интерфейс, который можно использовать и в ArrayAdapter и в SimpleAdapter и т.д.
- SpinnerAdapter — адаптер для связки данных с элементом Spinner. Это тоже интерфейс, как ListAdapter и работает по схожему принципу
- SimpleAdapter — адаптер, позволяющий заполнить данными список более сложной структуры, например, два текста в одной строке списка.
- SimpleCursorAdapter — дополняет ResourceCursorAdapter и создаёт компоненты TextView/ImageView из столбцов, содержащихся в курсоре. Компоненты определяются в ресурсах
- CursorAdapter — предназначен для работы с ListView, предоставляет данные для списка через курсор, который должен иметь колонку с именем «_id»
- ResourceCursorAdapter — этот адаптер дополняет CursorAdapter и может создавать виды из ресурсов
- HeaderViewListAdapter — расширенный вариант ListAdapter, когда ListView имеет заголовки.
- WrapperListAdapter — еще один адаптер для списков.
BaseAdapter
Стандартные адаптеры не всегда покрывают потребности программиста. Если вам нужен свой собственный адаптер, то в Android есть абстрактный класс BaseAdapter, который можно расширить. Собственный адаптер необходим в тех случаях, когда требуется специальное управление данными или дополнительный контроль над отображением дочерних представлений. Кроме того, вы можете предусмотреть в своём адаптере элементы кэширования для повышения производительности работы.
Пример использования адаптере на основе BaseAdapter можно увидеть при создании GridView с картинками и в других примерах.
У BaseAdapter есть несколько методов, которые следует переопределить. Например, метод getCount() позволяет узнать количество выводимых объектов.
Другой важный метод адаптера — getView(), который отвечает за создание отдельных элементов списка. Он вызывается для каждого элемента списка, чтобы определить, какие данные нужно отобразить. Метод getVew() содержит параметр convertView, который позволяет использовать заново уже существующий элемент списка, который не отображается, т.к. пользователь пролистнул его с видимой части дисплея. Если convertView не пустой, он может быть использован заново, чтобы не грузить разметку списка. Подобный подход способствует увеличению производительности.
Метод getView() возвращает View, который фактически является контейнером ViewGroup и содержит в себе другие компоненты, например, ImageView или TextView.
На сайте представлены (будут представлены) практически все примеры с адаптерами. Оставайтесь на связи!
Источник
SimpleAdapter
Конструктор класса SimpleAdapter имеет следующий вид:
Для данных в параметре data используется коллекция Map-объектов или его наследников, например, HashMap. Каждый Map содержит данные для отдельного элемента списка. Чтобы адаптер понимал, какие данные нужно вставлять в View-компоненты каждого пункта списка, мы указываем два массива from и to. В массиве from используем ключи из Map, а в массиве to – идентификаторы компонентов. Адаптер последовательно перебирает все компоненты из массива to и сопоставляет им соответствующие значения из from. Следите, чтобы в массиве to было не больше элементов, чем в from, иначе программа вызовет ошибку.
Используем системную разметку
При работе с адаптером ArrayAdapter для ListView мы использовали готовую разметку android.R.layout.simple_list_item_1, которая состоит из одного TextView. Существует другая системная разметка android.R.layout.simple_list_item_2, состоящая из двух текстовых меток с идентификаторами android.R.id.text1 и android.R.id.text2, расположенных в двух строчках. В первой метке выводится текст большим шрифтом, а во втором — маленьким.
При желании можно было наследоваться от ArrayAdapter и реализовать работу с такой разметкой. Но SimpleAdapter уже оптимизирован для работы с таким случаем, поэтому мы можем сразу использовать его.
Напишем пример, где у каждого кота будет его имя и телефон.
Мы создали два ключа Name и Tel и программно заполняем их именами котов и их телефонами. Все телефоны вымышлены, не надо звонить по ним! Коты не дают свои номера кому попало.
Используем собственную разметку
Допустим, мы хотим вывести список котов в три колонки: номер, имя кота и его телефон (Ух, ты).
Создадим отдельную разметку для каждого элемента списка:
Код для связывания адаптера со списком:
Результат будет следующим:
Изменения минимальны. Мы для каждого кота добавили порядковый номер MemberID, а в адаптере указали не системную, а собственную разметку R.layout.list_item. А также используем массив из трёх элементов.
ListAdapter
В приведённых примерах вы можете безбоязненно заменить SimpleAdapter на ListAdapter:
Это на тот случай, чтобы вы не пугались, если встретите такой код.
Сложная разметка
Разметка может быть любой и включать в себя не только текстовые метки, но и другие компоненты, такие как ImageView и CheckBox. Принцип остаётся такой же. Создаём новую разметку и связываем данные с каждым компонентом.
Создадим новую разметку (или отредактируем файл list_item.xml из предыдущего примера)
Смотрим, какие тут отличия. Теперь мы используем не только строки для имён котов, но и булево значение для флажка и целое число для значка. Поэтому, выражение HashMap заменим на более универсальный HashMap
Смотрим на результат. Я знаю только одного кота, который слушает да ест. Поэтому пометим его как накормленного по умолчанию.
Мы создали два массива, которые будут использованы для сопоставления данных и компонентов. Массив from содержит имена Map-ключей, а массив to – идентификаторы компонентов. Таким образом, в первый TextView с идентификатором R.id.textview_name будет вставлено первое значение ключа CatName и так далее. Аналогично происходит и с другими компонентами CheckBox и ImageView.
Адаптер перебирает все компоненты из массива to для каждого элемента списка и сопоставляет им значения из массива from. Получается такая таблица:
R.id.R.id.textview_name – Map.get(«CatName»)
R.id.checkbox_feed — Map.get(«IsHungry»)
R.id.imageview_cat — Map.get(«Icon»)
Тут возникает интересный вопрос, а как адаптер понимает, какой именно метод нужно вызвать для компонента, чтобы передать ему значение. К примеру, для компонента CheckBox нам нужно установить установить флажок или снять его, а не выводить текст, как для TextView.
Тут о нас уже позаботились. Адаптер смотрит, с чем имеет дело и в зависимости от типа компонента принимает решение. Вариантов немного, всего три.
Первый вариант — если компонент является TextView или его наследником, то вставляется текст. Иными словами, вызывается метод SimpleAdapter.setViewText(), который вызывает TextView.setText() и передает туда нужное значение из Map.
Второй вариант — если компонент, наследует интерфейс Checkable, то адаптер проверяет, является ли соответствующее значение из Map типа boolean. Если это действительно так, то вызывается метод Checkable.setChecked. Если значение не является типом boolean, то проверяется, является ли компонент наследником TextView. Если ответ положительный, то тогда вставляется текстовое значение из Map. В других случаях мы получим ошибку. К компонентам с интерфейсом Checkable относятся CheckBox, CheckedTextView, CompoundButton, RadioButton, Switch, ToggleButton. Вы можете сами в этом убедиться. Заменим блок CheckBox на TextView в разметке list_item.xml:
Запустим проект и увидим следующее:
К счастью, приложение не завершается с ошибкой, а просто выводит значения булевой переменной.
Третий вариант — если компонент является ImageView или его наследником, то проверяется тип данных. Если тип можно привести к типу Integer, то вызывается метод SimpleAdapter.setViewImage(ImageView v, int value), который вызывает метод ImageView.setImageResource(), т.е. подставляется идентификатор ресурса из папки drawable или mipmap. Если тип другой, то вызывается метод SimpleAdapter.setViewImage (ImageView v, String value), который пытается привести значение к int и вызвать метод ImageView.setImageResource(). Если такой вариант не проходит, то преобразует строку в объект Uri и вызывает метод ImageView.setImageURI(Uri).
Если компонент не подходит ни под один из трёх вышеперечисленных типов, то мы получим ошибку.
Продолжим опыты. Допустим, мы хотим не только установить флажок у CheckBox, но и новый текст. Так как мы уже знаем, что наследники TextView могут выводить текст, то поступим следующим образом. В наши массивы from и to добавим новые элементы:
Источник
Полный список
— создаем свой адаптер на основе BaseAdapter
Предоставляемые нам адаптеры универсальны и полезны, но иногда их возможностей не хватает для реализации задуманного. Тогда возникает необходимость написать свой адаптер. Попробуем и мы. Создавать будем не с нуля, а используя BaseAdapter.
Сделаем подобие интернет магазина. Будем выводить список товаров. Каждый пункт списка будет содержать название товара, цену и изображение. Также будет возможность отметить пункт галкой, поместив его тем самым в корзину.
Внизу списка сделаем кнопку, которая будет отображать содержимое корзины. В настоящем интернет-магазине мы повесили бы на нее, например, переход к созданию заказа.
Project name: P0541_CustomAdapter
Build Target: Android 2.3.3
Application name: CustomAdapter
Package name: ru.startandroid.develop.p0541customadapter
Create Activity: MainActivity
В файл strings.xml добавим текстовый параметр для названия кнопки.
layout для пункта списка – item.xml:
Чекбокс, пара текстовых полей и картинка.
Теперь пишем код. Можно все написать в MainActivity.java, но тогда он получится достаточно большим и неудобным для чтения. Я раскидаю весь код по трем классам.
Product.java – класс, описывающий товар:
Тут все просто – только конструктор и элементы класса. Не заморачиваюсь с доступом и методами Set/Get, чтобы не усложнять код.
BoxAdapter.java – созданный адаптер, который будем использовать
На всякий случай напомню общий принцип действия адаптера: он получает данные и выдает View для отображения пункта списка.
Смотрим код. В конструкторе мы заполняем наши внутренние переменные и получаем LayoutInflater для работы с layout-ресурсами. В objects у нас теперь хранится список товаров, которые надо отобразить в списке.
Методы, отмеченные аннотацией @Override, мы обязаны реализовать при наследовании BaseAdapter. Эти методы используются списком и должны работать корректно.
Метод getCount должен возвращать кол-во элементов. Мы возвращаем кол-во товаров.
Метод getItem должен возвращать элемент по указанной позиции. Используя позицию, получаем конкретный элемент из objects.
Метод getItemId должен возвращать id элемента. Здесь не заморачиваемся и возвращаем позицию. Кстати, также сделано в некоторых адаптерах. Поэтому мы и видели в обработчиках, что >
Метод getView должен возвращать View пункта списка. Для этого мы создавали layout-ресурс R.layout.item. В этом методе мы должны из R.layout.item создать View, заполнить его данными и отдать списку. Но перед тем как создавать, мы пробуем использовать convertView, который идет на вход метода. Это уже созданное ранее View, но неиспользуемое в данный момент. Например, при прокрутке списка, часть пунктов уходит за экран и их уже не надо прорисовывать. View из этих «невидимых» пунктов используются для новых пунктов. Нам остается только заполнить их данными. Это значительно ускоряет работу приложения, т.к. не надо прогонять inflate лишний раз.
Если же convertView в этот раз нам не дали (null), то создаем сами view. Далее заполняем наименования, цену и картинку из данных по товарам. Для чекбокса мы присваиваем обработчик, сохраняем в Tag позицию элемента и ставим галку, если товар уже в корзине.
Tag – это некое Object-хранилище у каждого View, куда вы можете поместить нужные вам данные. В нашем случае я для каждого чекбокса помещаю в его Tag номер позиции пункта списка. Далее в обработчике чекбокса я смогу этот номер позиции извлечь и определить, в каком пункте списка был нажат чекбокс.
В итоге, метод getView возвращает списку полностью заполненное view, и список его отобразит как очередной пункт.
Далее идет пара методов, которые не обязательно было создавать при наследовании BaseAdapter. Я их создал для удобства.
Метод getProduct – это аналог getItem, но он сразу конвертирует Object в Product. Он используется всего пару раз. И в принципе, можно было бы и без него обойтись.
Метод getBox проверяет, какие товары отмечены галками и формирует из них коллекцию-корзину.
myCheckChangeList – обработчик для чекбоксов. Когда мы нажимаем на чекбокс в списке, он срабатывает, читает из Tag позицию пункта списка и помечает соответствующий товар, как положенный в корзину.
Тут важно понимать, что без этого обработчика не работало бы помещение товаров в корзину. Да и на экране — значения чекбоксов в списке терялись бы при прокрутке. Потому что пункты списка пересоздаются, если они уйдут «за экран» и снова появятся. Это пересоздание обеспечивает метод getView, а он для заполнения View берет данные из товаров. Значит при нажатии на чекбокс, обязательно надо сохранить в данных о товаре то, что он теперь в корзине.
Остается накодить MainActivity.java:
Тут кода совсем мало.
В onCreate создаем адаптер и список.
В методе fillData генерируем данные для адаптера. В качестве картинки используем стандартную для всех пунктов. В идеале, для каждого товара своя картинка.
Метод showResult получает из адаптера список товаров корзины и выводит их наименования. Этот метод вызывается по нажатию кнопки на экране, т.к. прописан в ее свойстве onClick.
Все сохраняем и запускаем. Отмечаем товары и жмем кнопку для просмотра содержимого корзины.
Достаточно непростой получился пример из-за чекбокса.
Вполне может быть, что есть другой способ реализации этого примера. Но смысл был в том, чтобы показать создание своего адаптера. Для закрепления темы посмотрите еще этот гугловский пример.
На следующем уроке:
— используем Header и Footer в списках
— разбираемся, как и где используется HeaderViewListAdapter
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник