最近两天学习实现了一个功能,感觉很好,一定要记录下来。
在网上找了一些资料,林林总总,总是不那么齐全,有的代码做成小Demo还会报错,需要自己调试半天。也幸好如此,我将此功能涉及到的一些知识点理解的更加深刻一些。
功能需求:设计一个列表,类似qq好友列表,点击一级标题(对应:组名称),展开二级内容(对应:好友列表),再点击,则收起二级内容。一级标题下有一按钮,随着列表的展开收起自动的跟在一级列表下。
该功能使用了Android中的一个控件:ExpandableListView。
ExpandableListView是ListView的子类,它在普通的ListView的基础上进行了拓展,它把应用中的列表分为几组,每组又包含多个列表项。它的用法和ListView很像,只是其所显示的列表项应该由ExpandableListAdapter提供。下面提供该控件一些属性说明:
android:groupIndicator ———————————— 显示在组列表旁边的Drawable对象:可以是一个图片。
android:childIndicator ———————————— 显示在子列表旁边的Drawable对象
android:childDivider ———————————— 指定各组内子类表项之间的分割条:图片不会完全显示,分离子列表项的是一条直线。
android:childIndicatorRight —————————— 子列表项指示符的右边约束位置:表示左端从什么位置开始。
android:indicatorLeft ———————————— 组列表项指示器的左边约束位置
注意:在XML文件中,如果ExpandableListView上一级视图的大小没有严格定义的话,则不能对ExpandableListView的android:layout_height属性使用wrap_content值。(例如,如果上一级视图是ScrollView的话,则不应该指定wrap_content的值,因为它可以是任意的长度。不过,如果ExpandableListView的上一级视图有特定的大小的话,比如100像素,则可以使用wrap_content)。
适用于ExpandableListView的Adapter要继承BaseExpandableListAdapter,且必须重载getGroupView和getChildView两个最重要的方法。使用BaseExpandableListAdapter时,需要重载一下方法。
/** * 取得分组数 * * @return 组数 */ @Override public int getGroupCount() { // TODO Auto-generated method stub return 0; } /** * 取得指定分组的子元素数 * * @param groupPosition * :要取得子元素个数的分组位置 * @return:指定分组的子元素个数 */ @Override public int getChildrenCount(int groupPosition) { // TODO Auto-generated method stub return 0; } /** * 取得与给定分组关联的数据 * * @param groupPosition * 分组的位置 * @return 指定分组的数据 */ @Override public Object getGroup(int groupPosition) { // TODO Auto-generated method stub return null; } /** * 取得与指定分组、指定子项目关联的数据 * * @param groupPosition * :包含子视图的分组的位置 * @param childPosition * :指定的分组中的子视图的位置 * @return 与子视图关联的数据 */ @Override public Object getChild(int groupPosition, int childPosition) { // TODO Auto-generated method stub return null; } /** * 取得指定分组的ID.该组ID必须在组中是唯一的.必须不同于其他所有ID(分组及子项目的ID) * * @param groupPosition * 要取得ID的分组位置 * @return 与分组关联的ID */ @Override public long getGroupId(int groupPosition) { // TODO Auto-generated method stub return 0; } /** * 取得给定分组中给定子视图的ID.该组ID必须在组中是唯一的.必须不同于其他所有ID(分组及子项目的ID) * * @param groupPosition * 包含子视图的分组的位置 * @param childPosition * 要取得ID的指定的分组中的子视图的位置 * @return 与子视图关联的ID * */ @Override public long getChildId(int groupPosition, int childPosition) { // TODO Auto-generated method stub return 0; } /** * 是否指定分组视图及其子视图的id对应的后台数据改变也会保持该id * * @return 是否相同的id总是指向同一个对象 */ @Override public boolean hasStableIds() { // TODO Auto-generated method stub return false; } /** * 去的用于显示给定分组的视图,该方法仅返回分组的视图对象。 * * @param groupPosition * :决定返回哪个视图的组位置 * @param isExpanded * :该分组是展开状态(true)还是收起状态(false) * @param convertView * :如果可能,重用旧的视图对象.使用前你应该保证视图对象为非空,并且是否是合适的类型. * 如果该对象不能转换为可以正确显示数据的视图 ,该方法就创建新视图.不保证使用先前由getGroupView(int, * boolean,View, ViewGroup)创建的视图. * @param parent * :该视图最终从属的父视图 * * @return 指定位置相应的组试图 * */ @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { // TODO Auto-generated method stub return null; } /** * 取得显示给定分组给定子位置的数据用的视图 * * @param groupPosition * :包含要取得子视图的分组位置。 * @param childPosition * :分组中子视图(要返回的视图)的位置。 * @param isLastChild * :该视图是否为组中的最后一个视图。 * * @param convertView * : 如果可能,重用旧的视图对象,使用前应保证视图对象为非空,且是否是适合的类型。 * 如果该对象不能转换为正确显示数据的视图,该方法就创建新视图。 * 不保证使用先前由getChildView(int,int,boolean,View,ViewGroup)创建的视图。 * @param parent * :该视图最终从属的父视图 * * @return:指定位置相应的子视图。 */ @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { // TODO Auto-generated method stub return null; } /** * 指定位置的子视图是否可选择 * * @param groupPosition * 包含要取得子视图的分组位置 * @param childPosition * 分组中子视图的位置 * @return 是否子视图可选择 */ @Override public boolean isChildSelectable(int groupPosition, int childPosition) { // TODO Auto-generated method stub return false; }基础知识已经总结的差不多了,话不多说,直接上代码!!!!!!
下面我将我的部分源代码附上。
我的这个列表是显示在一个fragment中的,如果显示在activity中将会更简单。
1. fragment_cars_list.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ExpandableListView android:id="@+id/android_list" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_load_more" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/android_list" android:layout_centerHorizontal="true" android:padding="20dp" android:text="@string/load_more" /> </RelativeLayout>2. group.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" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <ImageView android:id="@+id/iv_selector" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" /> <TextView android:id="@+id/tv_group" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="6dp" android:paddingLeft="10dp" android:paddingTop="6dp" android:text="@string/list" android:textSize="15sp" /> </LinearLayout> </LinearLayout>3. child.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" android:orientation="vertical" > <TextView android:id="@+id/tv_child" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingBottom="10dp" android:paddingLeft="60dp" android:paddingTop="10dp" android:text="好友列表" android:textSize="20sp" /> </LinearLayout>4. BuddyAdapter.java
public class BuddyAdapter extends BaseExpandableListAdapter { private String[] group; private String[] buddy; private Context context; private LayoutInflater inflater; public BuddyAdapter(String[] group, String[] buddy, Context context) { super(); this.group = group; this.buddy = buddy; this.context = context; inflater = LayoutInflater.from(context); } @Override public int getGroupCount() { return group.length; } @Override public int getChildrenCount(int groupPosition) { return buddy.length; } @Override public Object getGroup(int groupPosition) { return group[groupPosition]; } @Override public Object getChild(int groupPosition, int childPosition) { return buddy[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) { convertView = inflater.inflate(R.layout.group, null); TextView groupNameTextView = (TextView) convertView .findViewById(R.id.tv_group); ImageView ivSelector = (ImageView) convertView .findViewById(R.id.iv_selector); groupNameTextView.setText(getGroup(groupPosition).toString()); ivSelector.setImageResource(R.drawable.selector_close); // 更换展开分组图片 if (!isExpanded) { ivSelector.setImageResource(R.drawable.selector); } return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { convertView = inflater.inflate(R.layout.child, null); TextView nickTextView = (TextView) convertView .findViewById(R.id.tv_child); nickTextView.setText(getChild(groupPosition, childPosition).toString()); return convertView; } // 子选项是否可以选择 @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } }4. carListFragment.java
public class CarsListFragment extends Fragment { private ExpandableListView elvCompany; private TextView tvLoadMore; // 群组名称(一级条目内容) private String[] group = new String[] { "我的好友" }; private String[] carsList = new String[] { "张三", "李四", "王五", "赵六", "天气" }; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_cars_list, null); tvLoadMore = (TextView) view.findViewById(R.id.tv_load_more); elvCompany = (ExpandableListView) view.findViewById(R.id.android_list); BuddyAdapter adapter = new BuddyAdapter(group, carsList, getContext()); elvCompany.setAdapter(adapter); setListeners(); return view; } private void setListeners() { // 分组展开 elvCompany.setOnGroupClickListener(new OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { return false; } }); // 分组关闭 elvCompany.setOnGroupCollapseListener(new OnGroupCollapseListener() { @Override public void onGroupCollapse(int groupPosition) { } }); // 子项点击 elvCompany.setOnChildClickListener(new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { Toast.makeText(getActivity(), group[groupPosition] + ":" + carsList[childPosition], Toast.LENGTH_SHORT).show(); return false; } }); tvLoadMore.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getActivity(), "没有更多数据了", Toast.LENGTH_SHORT) .show(); } }); } }运行之后,功能实现,但是会发现一个问题,在组列表左侧有一个默认的图标,如图所示:
那么我现在不要这个默认的图标使用我自己的图标,怎么设置呢?
其实很简单很简单,只需要设置一个属性值即可实现。我在fragment_cars_list.xml文件中的ExpandableListView控件设置属性android:groupIndicator="@null"即可实现效果。
该功能的实现特别感谢一篇文章的作者:
http://www.open-open.com/lib/view/open1406014566679.html