В данной статье хочу привести небольшой пример работы с кастомизацией ExpandableListView — двухуровневого списка.
То, что должно получиться в итоге

Приступим, создадим проект и добавим в активити данный код
Добавляем в main.xml ExpandableListView
Теперь создадим класс адаптера
Названия методов и параметров довольно информативны. Методы getGroupView и getChildView возвращают View для «родителей» и «детей» соответственно. Используя параметр isExpanded в методе getGroupView, можно, например, менять фон group при разных состояниях. С помощью LayoutInflater используем кастомные layout для нашего списка.
group_view.xml
child_view.xml
В child_view.xml добавлена кнопка, а в адаптере в методе getChildView обработали ее нажатие. Таким же образом можно добавлять кнопки и другие элементы в group_view.xml.
Так же списку можно «навесить» слушателей:
Теперь рассмотрим groupIndicator — индикатор состояния группы. Его положение задано в main.xml параметрами indicatorLeft и indicatorRight — соответственно левой и правой границей. По умолчанию индикатор располагается слева, что не очень привычно. Также можно подставить свои изображения, для этого нужно создать indicator.xml в папке drawable с таким кодом
Где imageOpen – будет отображаться при раскрытой группе, а imageClose – закрытой. Далее в main.xml нужно добавить строчку к параметрам нашего списка android:groupIndicator="@drawable/indicator". При подготовке изображений нужно учесть то, что они будут растянуты по всей высоте лэйаута group_view. Так что если нужен кастомный значок индикатора — лучше использовать пару изображений и контролировать их появления в методе getView. В этой статье хорошо описаны нюансы работы с адаптерами, а также хочу обратить ваше внимание на использование класса ViewHolder.
Ну вот и все, надеюсь, что пост поможет начинающим разработчикам.
То, что должно получиться в итоге

Приступим, создадим проект и добавим в активити данный код
public class ExpActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Находим наш list ExpandableListView listView = (ExpandableListView)findViewById(R.id.exListView); //Создаем набор данных для адаптера ArrayList<ArrayList<String>> groups = new ArrayList<ArrayList<String>>(); ArrayList<String> children1 = new ArrayList<String>(); ArrayList<String> children2 = new ArrayList<String>(); children1.add("Child_1"); children1.add("Child_2"); groups.add(children1); children2.add("Child_1"); children2.add("Child_2"); children2.add("Child_3"); groups.add(children2); //Создаем адаптер и передаем context и список с данными ExpListAdapter adapter = new ExpListAdapter(getApplicationContext(), groups); listView.setAdapter(adapter); } }
Добавляем в main.xml ExpandableListView
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ExpandableListView android:id="@+id/exListView" android:layout_width="match_parent" android:layout_height="match_parent" android:indicatorLeft="250dp" android:indicatorRight="300dp" /> </LinearLayout>
Теперь создадим класс адаптера
public class ExpListAdapter extends BaseExpandableListAdapter { private ArrayList<ArrayList<String>> mGroups; private Context mContext; public ExpListAdapter (Context context,ArrayList<ArrayList<String>> groups){ mContext = context; mGroups = groups; } @Override public int getGroupCount() { return mGroups.size(); } @Override public int getChildrenCount(int groupPosition) { return mGroups.get(groupPosition).size(); } @Override public Object getGroup(int groupPosition) { return mGroups.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return mGroups.get(groupPosition).get(childPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return true; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.group_view, null); } if (isExpanded){ //Изменяем что-нибудь, если текущая Group раскрыта } else{ //Изменяем что-нибудь, если текущая Group скрыта } TextView textGroup = (TextView) convertView.findViewById(R.id.textGroup); textGroup.setText("Group " + Integer.toString(groupPosition)); return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.child_view, null); } TextView textChild = (TextView) convertView.findViewById(R.id.textChild); textChild.setText(mGroups.get(groupPosition).get(childPosition)); Button button = (Button)convertView.findViewById(R.id.buttonChild); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(mContext,"button is pressed",5000).show(); } }); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } }
Названия методов и параметров довольно информативны. Методы getGroupView и getChildView возвращают View для «родителей» и «детей» соответственно. Используя параметр isExpanded в методе getGroupView, можно, например, менять фон group при разных состояниях. С помощью LayoutInflater используем кастомные layout для нашего списка.
group_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textGroup" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_marginLeft="5dp" android:layout_marginTop="20dp" android:textColor="@android:color/white" android:textStyle="bold" /> </LinearLayout>
child_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textChild" android:layout_width="wrap_content" android:layout_height="40dp" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:textColor="@android:color/white" /> <Button android:id="@+id/buttonChild" android:layout_width="100dp" android:layout_height="40dp" android:layout_marginLeft="150dp" android:layout_marginTop="10dp" android:text="Button" android:focusable="false" /> </LinearLayout>
В child_view.xml добавлена кнопка, а в адаптере в методе getChildView обработали ее нажатие. Таким же образом можно добавлять кнопки и другие элементы в group_view.xml.
Так же списку можно «навесить» слушателей:
- OnChildClickListener — нажатие на элемент
- OnGroupCollapseListener – сворачивание группы
- OnGroupExpandListener – разворачивание группы
- OnGroupClickListener – нажатие на группу
Теперь рассмотрим groupIndicator — индикатор состояния группы. Его положение задано в main.xml параметрами indicatorLeft и indicatorRight — соответственно левой и правой границей. По умолчанию индикатор располагается слева, что не очень привычно. Также можно подставить свои изображения, для этого нужно создать indicator.xml в папке drawable с таким кодом
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_expanded="true" android:drawable="@drawable/imageOpen"> </item> <item android:state_empty="true" android:drawable="@drawable/imageClose"> </item> </selector>
Где imageOpen – будет отображаться при раскрытой группе, а imageClose – закрытой. Далее в main.xml нужно добавить строчку к параметрам нашего списка android:groupIndicator="@drawable/indicator". При подготовке изображений нужно учесть то, что они будут растянуты по всей высоте лэйаута group_view. Так что если нужен кастомный значок индикатора — лучше использовать пару изображений и контролировать их появления в методе getView. В этой статье хорошо описаны нюансы работы с адаптерами, а также хочу обратить ваше внимание на использование класса ViewHolder.
Ну вот и все, надеюсь, что пост поможет начинающим разработчикам.