В предыдущем посте мы говорили о ListView и о том, как мы можем его использовать. Android OS имеет другой виджет, который может быть полезен, когда вы хотите сгруппировать элементы, этот компонент называется ExpandableListView . Этот компонент имеет возможность группировать элементы, и когда пользователь нажимает на группу, он раскрывает элементы.
Этот компонент можно использовать, когда у нас длинный список элементов, и его можно разделить на группы, чтобы организовать их и сделать вещи менее запутанными.
Когда пользователь нажимает на группу, она расширяется, и мы имеем:
Как показано выше, нам нужны два разных макета: один для группы и другой для элементов. Так что это немного сложнее, чем listView, даже если концепция этого компонента такая же. Нам нужно разработать адаптер и две раскладки.
Предположим, у нас есть два класса, которые представляют нашу модель: один — это класс Category, а другой — ItemDetail . Category содержит список ItemDetail, поэтому класс Category похож на группу (см. Выше), а ItemDetail — на элемент.
Здесь есть фрагмент кода:
01
02
03
04
05
06
07
08
09
10
|
public class Category implements Serializable { private long id; private String name; private String descr; private List<ItemDetail> itemList = new ArrayList<ItemDetail>(); ... } |
1
2
3
4
5
6
7
8
|
public class ItemDetail implements Serializable { private long id; private int imgId; private String name; private String descr; .... } |
Теперь пришло время создать макет нашей группы. Мы сделаем это по-настоящему простым: всего две строки: одна для имени категории, а другая для ее описания, поэтому у нас есть:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
<? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "match_parent" android:layout_height = "match_parent" > < TextView android:id = "@+id/groupName" android:layout_width = "match_parent" android:layout_height = "match_parent" android:paddingLeft = "15dip" /> < TextView android:id = "@+id/groupDescr" android:layout_width = "match_parent" android:layout_height = "match_parent" android:layout_alignParentLeft = "true" android:layout_below = "@id/groupName" android:textSize = "8dip" /> </ RelativeLayout > |
Теперь нам нужен другой макет для элементов, поэтому мы имеем:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
<? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "match_parent" android:layout_height = "match_parent" > < TextView android:id = "@+id/itemName" android:layout_width = "match_parent" android:layout_height = "match_parent" android:layout_alignParentLeft = "true" /> < TextView android:id = "@+id/itemDescr" android:layout_width = "match_parent" android:layout_height = "match_parent" android:layout_alignParentLeft = "true" android:layout_below = "@id/itemName" /> </ RelativeLayout > |
Мы сделали вещи действительно простыми. Теперь у нас есть наши макеты, нам нужно создать адаптер для обработки элементов и групп и связать их вместе.
ExpandableListView Adapter
Чтобы создать подходящий адаптер, мы должны расширить BaseExpandableListAdapter . Если вы прочтете документацию об этом абстрактном классе, то заметите, что нам нужно реализовать некоторые методы. Мы могли бы использовать SimpleExpandableListAdapter, который уже реализует некоторые методы, но мы хотим иметь более глубокий контроль над нашим адаптером.
Есть два метода, которые используются для создания представления, соответствующего нашему макету, один для элементов (дочерних элементов) и один для групп. Эти методы:
- Представление getChildView (int groupPosition, int childPosition, логическое isLastChild, View convertView, ViewGroup parent), используемое, когда нам нужно создать дочернее представление
- Представление getGroupView (int groupPosition, boolean isExpanded, View convertView, ViewGroup parent), используемое, когда нам нужно создать наше групповое представление.
Поэтому мы должны переопределить эти два метода, чтобы мы могли использовать наши макеты. Существуют и другие «вспомогательные» методы, которые нам необходимо реализовать, чтобы узнать общее число дочерних элементов (элементов в нашем случае), идентификаторы дочерних элементов, общее число групп (категорий) и идентификаторы групп.
Давайте сначала сосредоточим наше внимание на getChildView, здесь есть код
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Override public View getChildView( int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { View v = convertView; if (v == null ) { LayoutInflater inflater = (LayoutInflater)ctx.getSystemService (Context.LAYOUT_INFLATER_SERVICE); v = inflater.inflate(R.layout.item_layout, parent, false ); } TextView itemName = (TextView) v.findViewById(R.id.itemName); TextView itemDescr = (TextView) v.findViewById(R.id.itemDescr); ItemDetail det = catList.get(groupPosition).getItemList().get(childPosition); itemName.setText(det.getName()); itemDescr.setText(det.getDescr()); return v; } |
Как вы можете заметить в методе, мы получаем groupPosition и childPosition, которые определяют дочернюю позицию внутри категории (группы). Код довольно прост, мы просто раздуваем наш item_layout, а затем извлекаем сначала группу из списка категорий в groupPosition, а затем дочерний элемент (класс ItemDetail) внутри списка, содержащегося в классе Category.
1
|
ItemDetail det = catList.get(groupPosition).getItemList().get(childPosition); |
Когда нам нужно создать групповое представление, соответствующее нашему классу Category, мы просто делаем то же самое, как показано ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Override public View getGroupView( int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { View v = convertView; if (v == null ) { LayoutInflater inflater = (LayoutInflater)ctx.getSystemService (Context.LAYOUT_INFLATER_SERVICE); v = inflater.inflate(R.layout.group_layout, parent, false ); } TextView groupName = (TextView) v.findViewById(R.id.groupName); TextView groupDescr = (TextView) v.findViewById(R.id.groupDescr); Category cat = catList.get(groupPosition); groupName.setText(cat.getName()); groupDescr.setText(cat.getDescr()); return v; } |
А как насчет других методов? … Ну, они действительно тривиальны, и нам не нужно их объяснять.
В основном действии, которое создает наш ExpandableListView, мы имеем:
01
02
03
04
05
06
07
08
09
10
11
|
@Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); initData(); setContentView(R.layout.activity_main); ExpandableListView exList = (ExpandableListView) findViewById(R.id.expandableListView1); ExpandableAdapter exAdpt = new ExpandableAdapter(catList, this ); exList.setIndicatorBounds( 0 , 20 ); exList.setAdapter(exAdpt); } |
В приведенном выше коде мы просто получаем ссылку на виджет ExpandableListView в нашем основном макете и создаем адаптер. Одна вещь, которую вы должны заметить, это
1
|
exList.setIndicatorBounds( 0 , 20 ); |
Используя этот метод, мы устанавливаем границы индикатора. Индикатор — это значок, который рисуется рядом с группой, который меняется, если группа открыта или закрыта. Вот несколько изображений:
Изображение предметов внутри каждой категории