学习笔记,欢迎指导。
最近做了一个项目,需要一个层级列表,完成之后,就顺便来做个博客简介一下。
△基本简介
→“ExpandableListView”是对“ListView”做的扩展,“ListView”是个列表,而“ExpandableListView”则是分层的、可以扩展的、可折叠的,列表。在你学习“ExpandableListView”之前,最好已经能够熟练使用“ListView”及其优化,即“ListView”+“ArrayList”+“BaseAdapter”+“ViewHolder”(ListView的“四件套”),如果你能熟练使用“ListView”,那么“ExpandableListView”对你来说,只是加了一些功能,以及添加了一些API,很好理解。
→默认的“ExpandableListView”是两层的,效果基本就和QQ联系人的那个样子,两级。
→适配器:“ListView”可以匹配“BaseAdapter”适配器,而“ExpandableListView”对应匹配“BaseExpandableListAdapter"
△相关API简介
相比较于“ListView”,“ExpandableListView”引入了更多API(毕竟人家多一层啊),同时,其所用的适配器要重写的方法也更多,下面我们挑几个重要的来看,先了解一下这些API,看程序时就不会难理解。首先看看适配器里需要重写的API。
→public int getGroupCount();//表示分组的总个数,即一共有多少个组
→public int getChildrenCount(int groupPosition);//获取某个分组下子栏目个数
→public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent);//获取第“groupPosition"组的View对象,其中“isExpanded”表示当前位置是否展开。
→public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent);//获取第”groupPosition"组里面的"childPosition"位置上的View对象。
下面看看“ExpandableListView”本身的某一些方法:
→expandableListView.setGroupIndicator(Drawable drawable);//系统默认的“ExpandableListView"每个组View都带着一个箭头,你可以在这个方法里面设置箭头样式,如果传入”null"表示去掉默认箭头,系统自带那个箭头总是会在View的最左边,如果你想箭头放在其他地方,你就调用这个方法并传入空。
→expandableListView.setOnGroupClickListener(OnGroupClickListener onGroupClickListener);//设置每个“组”View的监听器。这里需要注意的是,“ExpandableListView"默认是会处理组View的点击事件,效果就是没按一下就会扩展对应的组、或者收起。在"OnGroupClickListener"方法里面的返回值,如果返回false,在执行完你自己的代码之后,系统还会执行默认点击事件(即展开或者收起组View),如果你在这个方法返回true,那么系统不再进行默认处理。
→expandableListView.setOnChildClickListener();//设置每个子View监听器
△示例效果
说了那么多的简介,进入正题,来看一下今天要实现的效果:
我们来做一个七大洲的国家显示,简化操作,只显示了部分国家,另外国家所在大洲来自网络,如有不对还请见谅,稍微说明一下这个效果:首先去掉了系统左边默认的箭头,其次在右边自己添加了箭头,当列表没有被展开,箭头向下,当列表被展开之后,对应的箭头就指向上面。另外需要说明的是,整个界面是在Fragment里面做的。
△Fragment布局文件代码
布局文件的名字“fragment_expandable",其实只有一个控件而已
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ExpandableListView
android:id="@+id/expandable_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ExpandableListView>
</LinearLayout>
△Fragment主要逻辑代码
Fragment主要逻辑代码包括:对数据进行初始化、对“ExpandableListView"做初始化。先来看看如何初始化数据的:
// 准备"ExpandableListView"用的数据
private void dataInit(){
// 先初始化组的数据
groupData = new String[]{"亚洲","非洲","北美洲","南美洲","南极洲","欧洲","大洋洲"};
// 初始化各个子栏目里的数据,下面各个大洲对应国家来自网络,如有错误还请见谅
childData = new String[7][];
childData[0] = new String[]{"中国","蒙古","朝鲜","韩国"};//亚洲
childData[1] = new String[]{"埃及","苏丹","利比亚","突尼斯"};//非洲
childData[2] = new String[]{"美国","加拿大"};//北美洲
childData[3] = new String[]{"哥伦比亚","委内瑞拉"};//南美洲
childData[4] = new String[]{"没有国家"};//南极洲
childData[5] = new String[]{"德国","英国","爱尔兰","意大利"};//欧洲
childData[6] = new String[]{"澳大利亚","瑙鲁共和国","瓦努阿图共和国","马绍尔群岛共和国"};//大洋洲
}
其中"groupData"以及"childData"都是成员变量来的,真实情况下你的数据可能是来自网络,或数据库,但是这里为了简便操作,我就直接用数组了。
现在看看“ExpandalbeListView"的初始化:
// "expandableListView"做初始化
private void viewInit(){
expandableListView = (ExpandableListView)layoutView.findViewById(R.id.expandable_list_view);//findViewById()找到控件
expandableListView.setGroupIndicator(null);//去掉控件默认箭头
expandableAdapter = new ExpandableAdapter(getActivity(),groupData,childData);//数据传递进适配器
expandableListView.setAdapter(expandableAdapter);//适配
}
至此为止和常规的"ListView"的主要的区别在于,1,他需要准备两套的数据,一个是组,一个是子。2,调用了个方法去掉箭头。
接着看看Fragment生命周期里的调用,也很简单:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
layoutView = inflater.inflate(R.layout.fragment_expandable,container,false);
dataInit();
viewInit();
return layoutView;
}
△自定义布局的代码
不论是“groupView”或者"childView"都自定义一个Item,现在看看“groupView”item布局文件,文件名字:item_expandable_group
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content" android:padding="8dp"
android:gravity="center_vertical">
<!-- 这个控件显示大洲名字-->
<TextView
android:id="@+id/tv_item_expandable_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="24sp"/>
<!-- 这个控件显示箭头-->
<ImageView
android:id="@+id/iv_item_expandable_group_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_down_arrow"/>
</LinearLayout>
这里共有两个控件,TextView用来显示大洲名字,ImageView用来显示自己那个箭头的。图片资源请自己找去啦。
然后看看“childView"item布局文件,文件名字:item_expandable_child
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent" android:padding="8dp">
<!-- 只要一个控件显示国家名字即可-->
<TextView
android:id="@+id/tv_item_expandable_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
只有一个控件用来显示国家名字
△适配器的逻辑代码
到此为止准备工作是做完了,现在来看我们的重头戏,"BaseExpandableListAdapter"代码,它里面的关键方法我已经在前面说了,另外下面代码也有相关注释,代码如下:public class ExpandableAdapter extends BaseExpandableListAdapter {
private Context context;//上下文
private String[] groupData;//组数据
private String[][] childData;//子数据
/**
* 构造方法
* @param context 上下文
* @param groupData "组"的数据
* @param childData "子"的数据
*/
public ExpandableAdapter(Context context,String[] groupData,String[][] childData){
this.context = context;
this.groupData = groupData;
this.childData = childData;
}
// 获取“组”个数
@Override
public int getGroupCount() {
return groupData.length;//返回组Item的个数
}
// 获取指定组里面的子Item的个数
@Override
public int getChildrenCount(int groupPosition) {
return childData[groupPosition].length;//返回某个组对应子Item个数
}
// 返回组Item的对象
@Override
public Object getGroup(int groupPosition) {
return groupData[groupPosition];//直接返回数据对象就行
}
// 返回子Item的对象
@Override
public Object getChild(int groupPosition, int childPosition) {
return childData[groupPosition][childPosition];//这里也是直接返回数据对象就行
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;//直接返回groupPosition
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return groupPosition*childPosition;//"groupPosition"和"chilPosition"结合一下再返回去
}
@Override
public boolean hasStableIds() {
return false;
}
/*
以下是两个ViewHolder,
分别用于组里面的和子栏目里的
*/
private class GroupViewHolder{
private TextView mainLandTv;//这个表示大洲名字
private ImageView arrowIv;
}
private class ChildViewHolder{
private TextView countryNames;//这个表示国家名字
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
// 以下代码和“ListView”优化的代码差不多,即ViewHolder的使用,这里就不再多解释
GroupViewHolder groupViewHolder = null;
if(convertView==null){
groupViewHolder = new GroupViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.item_expandable_group,null);
groupViewHolder.mainLandTv = (TextView)convertView.findViewById(R.id.tv_item_expandable_group);
groupViewHolder.arrowIv = (ImageView)convertView.findViewById(R.id.iv_item_expandable_group_arrow);
convertView.setTag(groupViewHolder);
}
groupViewHolder = (GroupViewHolder)convertView.getTag();
groupViewHolder.mainLandTv.setText(groupData[groupPosition]);//设置相对应的数据
// 根据当前栏目展开与否分别设置不同方向箭头
if(isExpanded){
groupViewHolder.arrowIv.setImageResource(R.drawable.ic_up_arrow);
}else{
groupViewHolder.arrowIv.setImageResource(R.drawable.ic_down_arrow);
}
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
// 这里也和ViewHolder使用方式一致,不多解释
ChildViewHolder childViewHolder = null;
if(convertView==null){
childViewHolder = new ChildViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.item_expandable_child,null);
childViewHolder.countryNames = (TextView)convertView.findViewById(R.id.tv_item_expandable_child);
convertView.setTag(childViewHolder);
}
childViewHolder = (ChildViewHolder)convertView.getTag();
childViewHolder.countryNames.setText(" "+childData[groupPosition][childPosition]);//将国家的名字设置上去
childViewHolder.countryNames.setTextSize(20);
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
}