ExpandableListView
Компонент ExpandableListView является расширенным вариантом компонента ListView. Основное отличие — разворачивающий список второго уровня. Получается список в списке. Рассмотрим простейший вариант:
Сам по себе компонент не представляет интереса. Его необходимо заполнить данными. Переключимся в код активности и напишем следующее:
Сначала мы описываем массивы данных – это названия групп (Времена года) и названия элементов для них (месяцы).
Затем описываем коллекцию для групп, коллекции для элементов и Map для атрибутов.
В методе onCreate() заполняем groupData. Это коллекция групп. Каждая группа представляет собой Map. А в Map мы пишем необходимые нам атрибуты для каждой группы. В нашем случае, для каждой группы мы укажем всего один атрибут groupName — это название из массива groups.
Адаптер обычно использует layout-ресурс для отображения пункта списка. В нашем случае пунктами ExpandableListView являются и группа и элемент. В layout-ресурсе могут быть какие-либо TextView. Мы можем заполнить их значениями из атрибутов элементов или групп, которые собраны в Map. Для этого нам надо указать сначала имена атрибутов, которые хотим использовать, а затем идентификаторы TextView, в которые хотим поместить значения этих атрибутов.
Для связки атрибутов и TextView мы используем два массива:
- groupFrom – список имен атрибутов, которые будут считаны. В нашем случае – это groupName, который мы добавили к группе с помощью Map чуть выше в коде, когда собирали группы в groupData.
- groupTo – список ID View-элементов, в которые будут помещены считанные значения атрибутов. Наш используемый layout будет содержать TextView с >Два этих массива сопоставляются по порядку элементов. В итоге, в layout-ресурсе группы найдется элемент с ID = android.R.id.text1 и в него запишется текст из атрибута groupName. Тем самым мы получим отображение имени группы в списке.
Далее формируем коллекции элементов. Создаем общую коллекцию коллекций. А затем создаем коллекции элементов каждой группы. Принцип тот же, что и с группами – создаем Map и в него пишем атрибут monthName со значением равным имени элемента. Коллекцию элементов для каждой группы добавляем в общую коллекцию.
Формируем два массива для сопоставления TextView из layout и атрибутов элементов. Полностью аналогично, как выше мы уже проделали с группами. В итоге при отображении элемента, найдется TextView с ID = android.R.id.text1 и туда запишется текст из атрибута monthName. И мы увидим текст нашего элемента (месяца) в списке.
В конце кода создаем адаптер SimpleExpandableListAdapter и присваиваем его списку.
Параметры для адаптера:
- this – контекст
- groupData – коллекция групп
- android.R.layout.simple_expandable_list_item_1 – layout-ресурс, который будет использован для отображения группы в списке. Соответственно, запросто можно использовать свой layout-файл
- groupFrom – массив имен атрибутов групп
- groupTo – массив ID TextView из layout для групп
- childData – коллекция коллекций элементов по группам
- android.R.layout.simple_list_item_1 — layout-ресурс, который будет использован для отображения элемента в списке. Можно использовать свой layout-файл childFrom – массив имен атрибутов элементов
- childTo — массив ID TextView из layout для элементов
Layout simple_expandable_list_item_1, который мы использовали для отображения групп – это TextView с отступом от левого края, чтобы осталось место для кнопки раскрытия/сворачивания списка. Для эксперимента вы можете попробовать использовать для групп layout simple_list_item_1, который мы использовали для элементов. В этом случае текст будет пересекаться с кнопкой.
А вообще вы можете создать для элементов свою разметку, например, с тремя TextView. И к каждому элементу списка (Map) добавить еще по два атрибута. Далее указываете вашу разметку в конструкторе, формируете соответственно массивы childFrom и childTo чтобы сопоставить атрибуты и TextView, и получится, что каждый элемент группы содержит более подробную информацию.
ExpandableListView редко используется в составе разметки с другими элементами. Обычно такой список занимает весь экран, поэтому для подобных целей лучше использовать специальный класс ExpandableListViewActivity, который уже содержит в своём составе компонент ExpandableListView.
Размещение индикаторов групп в заданном месте
Вы можете указать другое расположение индикаторов групп, например, справа. Для этого используются методы setIndicatorBounds() или setIndicatorBoundsRelative():
В примере использованы «магические числа». Вам лучше самостоятельно вычислить нужные значения.
Собственные индикаторы
Для создания собственных индикаторов приготовьте файлы *.9.png в двух состояниях (обычный и раскрытый) и пропишите их в drawable-ресурсах.
Пропишите селектор в атрибуте groupIndicator.
Источник
Полный список
— обрабатываем события дерева-списка
Дерево-список строить мы умеем, теперь посмотрим, как с ним можно взаимодействовать. Нам предоставлена возможность обрабатывать следующие события: нажатие на группу, нажатие на элемент, сворачивание группы, разворачивание группы.
Создадим проект:
Project name: P0461_ExpandableListEvents
Build Target: Android 2.3.3
Application name: ExpandableListEvents
Package name: ru.startandroid.develop.p0461expandablelistevents
Create Activity: MainActivity
TextView для вывода информации и ExpandableListView.
В проекте, рядом с классом MainActivity создадим (не Activity) класс AdapterHelper. В него поместим код для заполнения списка, чтобы разгрузить MainActivity.java.
Код создания адаптера полностью заимствован с прошлого урока. Чтобы получить адаптер нам надо будет просто вызвать метод getAdapter.
У класса есть конструктор, через который мы передаем объекту ссылку на context. Context нам понадобится, чтобы создать адаптер. Адаптеру же в свою очередь context нужен, например, для доступа к LayoutInflater.
В конце класса находятся методы, которые возвращают нам названия групп и элементов из коллекций по номеру группы или номеру элемента. Для этого используем методы адаптера getGroup и getChild, приводим их к Map и извлекаем значение атрибута с именем компании или телефона.
Пишем код в MainActivity.java:
Благодаря классу AdapterHelper, код создания адаптера занял всего две строчки: создание объекта и вызов метода getAdapter. Далее присваиваем адаптер списку и добавляем обработчики:
Метод
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id), где
parent – ExpandableListView с которым работаем
v – View элемента
groupPosition – позиция группы в списке
childPosition – позиция элемента в группе
id – id элемента
Мы выводим в лог позицию и id. А в TextView сверху от списка выводим текст нажатого элемента и его группы, который получаем с помощью методов AdapterHelper.
Метод должен вернуть boolean. Если мы возвращаем true – это значит, мы сообщаем, что сами полностью обработали событие и оно не пойдет в дальнейшие обработчики (если они есть). Если возвращаем false – значит, мы позволяем событию идти дальше.
Метод
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id), где
parent – ExpandableListView с которым работаем
v – View элемента
groupPosition – позиция группы в списке
id – id группы
Мы выводим в лог позицию и id группы.
Этот метод также должен вернуть boolean. Мы будет возвращать true, если позиция группы = 1, иначе — false. Т.е. для этой группы мы блокируем дальнейшую обработку события. Далее увидим, что нам это даст.
Метод
onGroupCollapse(int groupPosition), где groupPosition – позиция группы, которую свернули
Метод
onGroupExpand(int groupPosition), где groupPosition – позиция группы, которую развернули
И в конце кода MainActivity мы разворачиваем группу с позицией 2, используя метод expandGroup.
Все сохраним и запускаем.
Как видим, группа LG сразу развернута. Это сработала команда expandGroup в конце кода.
Если посмотреть в лог, то видим
onGroupExpand groupPosition = 2
Т.е. отработало событие разворачивания группы с позицией 2.
Нажмем, например, на Optimus Link. Смотрим лог:
onChildClick groupPosition = 2 childPosition = 1 >
Не забываем, что позиция считается с нуля. Группа с позицией 2 – LG, элемент с позицией 1 – Optimus Link, все верно.
Смотрим TextView сверху экрана, он считал из адаптера значение атрибута и отобразил его.
Теперь попробуем свернуть группу LG, нажмем на нее. Смотрим лог:
onGroupClick groupPosition = 2 > onGroupCollapse groupPosition = 2
Сначала отработал onGroupClick – нажатие на группу, а потом onGroupCollapse – сворачивание группы. TextView наверху экрана оповестил о том, что свернули группу LG.
Снова развернем группу LG. Лог:
onGroupClick groupPosition = 2 > onGroupExpand groupPosition = 2
Нажатие на группу и разворачивание. Обратите внимание, что при программном разворачивании, события нажатия не было, только разворот.
Теперь попробуем развернуть группу с позицией 1 – Samsung. Группа не разворачивается. Смотрим лог:
Событие нажатия есть, а вот обработчик разворачивания не вызывается. Это происходит из-за строчки
Мы для группы с позицией 1 блокируем дальнейшую обработку события и оно не уходит в обработчики разворачивания или сворачивания. Поэтому и не срабатывает onGroupExpand.
В итоге эти 4 обработчика позволяют вам определять, как пользователь взаимодействует с деревом.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Полный список
— строим список-дерево ExpandableListView
Если список элементов получается большой, имеет смысл разбить его на группы для упрощения навигации. Для этих целей можно использовать ExpandableListView. Это список в виде двухуровневого дерева. Первый уровень – группа, а в ней второй – элемент.
Чтобы построить такой список нам нужно как-то передать адаптеру данные по группам и элементам.
Каждая группа представляет из себя Map . Этот Map содержит атрибуты, которые вам нужны для каждой группы. Потом все эти Map (группы) собираются в List-коллекцию, например ArrayList. В итоге мы получили упакованные в один объект группы.
Каждый элемент группы также представлен объектом Map . Мы собираем все Map (элементы) для каждой группы в отдельную коллекцию. Получается, каждой группе соответствует коллекция с элементами. Далее эти коллекции мы теперь помещаем в общую коллекцию. Т.е. получается подобие двумерного массива. И в итоге пункты упакованы в один объект.
Сейчас начнем кодить пример и там станет понятнее.
List-коллекции называются обычно «список». Но т.к. список в контексте последних уроков — это набор пунктов на экране (ListView), то чтобы не путаться я буду использовать слово «коллекция».
Project name: P0451_ExpandableList
Build Target: Android 2.3.3
Application name: ExpandableList
Package name: ru.startandroid.develop.p0451expandablelist
Create Activity: MainActivity
Нарисуем экран main.xml:
Только ExpandableList на экране.
Код громоздкий и сложный, давайте разбираться.
Сначала мы в классе описываем массивы данных – это названия групп и названия элементов для них. Я решил в качестве данных выбрать смартфоны. Группы в нашем списке – это будут компании, а элементы – смартфоны этих компаний.
Затем описываем коллекцию для групп, коллекции для элементов и Map для атрибутов.
В методе onCreate заполняем groupData. Это коллекция групп. Каждая группа представляет собой Map. А в Map мы пишем необходимые нам атрибуты для каждой группы. В нашем случае, для каждой группы мы укажем всего один атрибут groupName — это название компании из массива groups.
Как мы помним, адаптер обычно использует layout-ресурс для отображения пункта списка. В нашем случае пунктами ListView являются и группа и элемент. В layout-ресурсе могут быть какие-либо TextView. Мы можем заполнить их значениями из атрибутов элементов или групп, которые собраны в Map. Для этого нам надо указать сначала имена атрибутов, которые хотим использовать, а затем ID TextView-элементов, в которые хотим поместить значения этих атрибутов. Речь сейчас идет о текстовых атрибутах. (Хотя вообще атрибут вовсе не обязан быть класса String)
Для связки атрибутов и TextView-элементов мы используем два массива:
groupFrom – список имен атрибутов, которые будут считаны. В нашем случае – это groupName, который мы добавили к группе с помощью Map чуть выше в коде, когда собирали группы в groupData.
groupTo – список ID View-элементов, в которые будут помещены считанные значения атрибутов. Наш используемый layout будет содержать TextView с >
Два этих массива сопоставляются по порядку элементов. В итоге, в layout-ресурсе группы найдется элемент с и в него запишется текст из атрибута groupName. Тем самым мы получим отображение имени группы (компании) в списке.
Далее формируем коллекции элементов. Создаем общую коллекцию коллекций. А затем создаем коллекции элементов каждой группы. Принцип тот же, что и с группами – создаем Map и в него пишем атрибут phoneName со значением равным имени элемента (телефона). Коллекцию элементов для каждой группы добавляем в общую коллекцию.
Формируем два массива для сопоставления TextView из layout и атрибутов элементов. Полностью аналогично, как выше мы уже проделали с группами. В итоге при отображении элемента, найдется TextView с и туда запишется текст из атрибута phoneName. И мы увидим текст нашего элемента (телефона) в списке.
В конце кода мы создаем адаптер SimpleExpandableListAdapter и присваиваем его списку.
На вход при создании адаптера идут элементы:
this – контекст
groupData – коллекция групп
android.R.layout.simple_expandable_list_item_1 – layout-ресурс, который будет использован для отображения группы в списке. Соответственно, запросто можно использовать свой layout-файл.
groupFrom – массив имен атрибутов групп
groupTo – массив ID TextView из layout для групп
childData – коллекция коллекций элементов по группам
android.R.layout.simple_list_item_1 — layout-ресурс, который будет использован для отображения элемента в списке. Можно использовать свой layout-файл
childFrom – массив имен атрибутов элементов
childTo — массив ID TextView из layout для элементов.
В общем непростая, на мой взгляд, реализация дерева получилась. Возможно, не сразу получится понять. Но я попытался расписать все досконально и как можно подробнее.
Layout simple_expandable_list_item_1, который мы использовали для отображения групп – это TextView с отступом от левого края, чтобы осталось место для кнопки раскрытия/сворачивания списка. Для эксперимента вы можете попробовать использовать для групп layout simple_list_item_1, который мы использовали для элементов. В этом случае текст будет пересекаться с кнопкой.
А вообще вы можете создать для элементов свой layout, например, с тремя TextView. И к каждому элементу списка (Map) добавить еще по два атрибута: цена и цвет. Далее указываете ваш layout в конструкторе, формируете соответственно массивы childFrom и childTo чтобы сопоставить атрибуты и TextView, и получится, что каждый элемент группы содержит более подробную информацию о смартфоне.
На следующем уроке:
— обрабатываем события дерева-списка
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник