当我们需要给ListView中的内容进行分组显示的时候,使用ListView就无法去完成这个需求了,这个时候我们就需要使用到ListView的子类ExpandableListView来实现内容的分组。ExpandableListView是什么东西呢?简单来讲就是一个增强版的ListView,它可以在点击父item来展开或收起列表,与之类似的就是我们经常使用的QQ中分组的功能,下面我们仿照QQ来使用ExpandableListView来实现一个简易的好友分组的功能。
先看运行效果:
下面来介绍下实现流程:
1.创建布局,这里需要使用到ExpandableListView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.wangke.myapplication.activity.ExpandableListViewActivity">
<ExpandableListView
android:id="@+id/ExpandableListView"
android:layout_width="match_parent"
android:layout_height="match_parent">
ExpandableListView>
LinearLayout>
2.准备数据源:
public class ExpandableListViewActivity extends AppCompatActivity {
private ExpandableListView expandableListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_expandable_list_view);
expandableListView = (ExpandableListView) findViewById(R.id.ExpandableListView);
//存放分组名称的ArrayListView
final ArrayList groupList = new ArrayList();
//给分组名称添加数据
groupList.add("特别关心");
groupList.add("偶像");
groupList.add("同学");
//创建一个ArrayList集合来存放特别关心分组下的child
ArrayList love = new ArrayList();
love.add(new MeiZi("特别关心1","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1491824845&di=269ad1b5e43c0bf82a0ce4b5f2a068e6&imgtype=jpg&er=1&src=http%3A%2F%2Fcimage.tianjimedia.com%2FuploadImages%2FthirdImages%2F2017%2F048%2F5RQB747RX375.jpg",0));
love.add(new MeiZi("特别关心2","http://img.hb.aicdn.com/d1505150d265f3b23c1d2b13adc73c5fe1bd715144395-qgVPEV_fw658",0));
love.add(new MeiZi("特别关心3","http://img.hb.aicdn.com/4d6704f2de492c23836ff1b02dd861e692b98bd76cc24-Opb07R_fw658",0));
// 创建一个ArrayList集合来存放偶像分组下的child
ArrayList star = new ArrayList();
star.add(new MeiZi("张玮玮","http://img.weixinyidu.com/151025/c9fe1f28.jpg",0));
star.add(new MeiZi("B哥","http://tc.sinaimg.cn/maxwidth.800/tc.service.weibo.com/chinambn_com/6aa8dc78de802571f95ba664a82eb161.png",0));
star.add(new MeiZi("左小老师","http://image.99ys.com/2015/0615/20150615103359430.jpg",0));
// 创建一个ArrayList集合来存放同学分组下的child
ArrayList classmate = new ArrayList();
classmate.add(new MeiZi("小明","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1491825460&di=15c1eaa59b4cbded96d5f8d604b99b4e&imgtype=jpg&er=1&src=http%3A%2F%2Fimg2.touxiang.cn%2Ffile%2F20170209%2Fc7bd37c60bc64328a4aeb46a905f0d42.jpg",0));
classmate.add(new MeiZi("小红","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1491230741097&di=fa2f30b3a185c3ef456390e0b532c2ee&imgtype=0&src=http%3A%2F%2Fwww.qqtouxiang.com%2Fd%2Ffile%2Fqinglv%2F20170212%2Fdef4f304850a84b899cb863aeacef31b.jpg",0));
classmate.add(new MeiZi("小莉","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1491230796582&di=152985d9d1a71b53997ee10b4dbc6018&imgtype=0&src=http%3A%2F%2Fdiy.qqjay.com%2Fu2%2F2014%2F1110%2Fa725f80999a062a27c22b6a747224060.jpg",0));
//创建一个泛型为>的HashMap来存放上面的数据,作为数据源,键为:分组名,值为每个分组对应的ArrayList
final HashMap> dataSet = new HashMap>();
dataSet.put(groupList.get(0),love);
dataSet.put(groupList.get(1),star);
dataSet.put(groupList.get(2),classmate);
expandableListView.setAdapter(new ExpandableListViewAdapter(groupList,dataSet,this));
看过上一篇ListView显示多种布局样式的哥们应该知道为什么child集合中的泛型类型是MeiZi,也是为了偷懒还是在之前的项目中实现的此功能,图省事就没有改Bean对象,当然也不影响大家观看,MeiZi就MeiZi吧^_^
3.这里我们要根据自己的需要来创建分组和分组内Item的布局样式:
item_expandablelistview_group.xml
(用于显示分组名的layout)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textSize="18sp"
android:gravity="center" />
LinearLayout>
item_expandablelistview_child.xml
(用于显示分组内item的layout)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.example.wangke.myapplication.customui.MyImageView
android:id="@+id/imageView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="10dp"
android:scaleType="centerCrop"
/>
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:gravity="center" />
LinearLayout>
4.创建适配器
我们在使用ListView的时候要创建适配器设置数据源来进行内容的显示,对于ExpandableListView来说也是一样的套路,这里我们在创建适配器需要继承自BaseExpandableListAdapter来实现其中的方法。
ExpandableListViewAdapter.java
/**
* Created by wangke on 17-4-3.
* ExpandableListView的适配器
*/
public class ExpandableListViewAdapter extends BaseExpandableListAdapter {
private ArrayList group;//存放分组名称的ArrayList
private HashMap> dataSet; //数据源
private Context context;
public ExpandableListViewAdapter(ArrayList group, HashMap> dataSet, Context context) {
this.group = group;
this.dataSet = dataSet;
this.context = context;
}
//获得Group的数量
@Override
public int getGroupCount() {
return group.size();
}
//获得child的数量
@Override
public int getChildrenCount(int groupPosition) {
//通过key(分组的名字)来从map中获取存放当前分组child的ArrayList集合并通过size获取大小
return dataSet.get(group.get(groupPosition)).size();
}
//获得某个Group对象
@Override
public Object getGroup(int groupPosition) {
//返回groupPosition对应的分组名称
return group.get(groupPosition);
}
//获得某个Group的child对象
@Override
public Object getChild(int groupPosition, int childPosition) {
//通过groupPosition和childPosition来取出某个分组中的子child
return dataSet.get(group.get(groupPosition)).get(childPosition);
}
//或的某个Group的Id
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
//获得某个Group的Child的Id
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
//该方法默认返回false
@Override
public boolean hasStableIds() {
return false;
}
//获得分组的View,下面的写法与ListView中getView的写法几乎一样,由于分组中的显示比较简单没有使用ViewHolder
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = View.inflate(context, R.layout.item_expandablelistview_group, null);
}
TextView tv_group = (TextView) convertView.findViewById(R.id.tv_group);
//通过groupPosition从group中获取对应的分组的名称
tv_group.setText(group.get(groupPosition));
return convertView;
}
//获得Child的View
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = View.inflate(context, R.layout.item_expandablelistview_child, null);
viewHolder = new ViewHolder();
convertView.setTag(viewHolder);
viewHolder.imageView = (MyImageView) convertView.findViewById(R.id.imageView);
viewHolder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.loadImageFromUrl(dataSet.get(group.get(groupPosition)).get(childPosition).getImageUri());
viewHolder.tv_name.setText(dataSet.get(group.get(groupPosition)).get(childPosition).getName());
return convertView;
}
class ViewHolder {
MyImageView imageView;
TextView tv_name;
}
//子选项是否可选中,如果需要设置child的点击事件,则需要返回true
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
是不是要实现的方法有点多?当然理解了之后也是挺简单的。
5.最后一步给适配器设置数据源:
expandableListView.setAdapter(new ExpandableListViewAdapter(groupList,dataSet,this));
写到这一步就能实现文章刚开始展示的效果了。
下面来介绍一下ExpandableListView中一些常用方法:
expandableListView.setChildDivider(); //设置分组中child的分割线的样式
expandableListView.setGroupIndicator();//如果不想使用系统自带的指示器,可以替换成自己喜欢的图片(左边用于指示当前页面分组是否展开的箭头)
常用的监听事件:
分组下child的点击事件监听:
//设置child点击的事件监听
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
return false;
}
});
分组的点击事件的监听:
//分组的监听事件
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
Log.i("wk","groupPosition:"+groupPosition);
return false;
}
});
分组被折叠的监听:
//分组折叠的监听
expandableListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
Log.i("wk","被折叠的分组:"+groupPosition);
}
});
长按事件的监听:
expandableListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
return false;
}
});
当我们在给ExpandableListView设置长按监听的时候,我们并不能根据所获取position的值找到所点击的项,原因是这个方法在点击的拿到的position值是根据你当前幕上已显示出来的条目来计算你当前点击的position的值是多少。如果想要在这个方法中获取我们所点击的具体是那个分组以及分组下的那个子item,我们就需用另一种方法,通过给我们点击的View设置tag来实现我们想要的功能。
实现步骤如下:
给适配器中的setChildView()方法中的convertView设置tag代码如下:
convertView.setTag(R.layout.item_expandablelistview_group,groupPosition);
convertView.setTag(R.layout.item_expandablelistview_child,childPosition);
setTag(key,object)中的key需要一个整型值,需要使用在资源文件中定义的整型值,直接写会报错。这里我直接使用布局文件对应的int值来作为key
给适配器中的getGroupView()方法中的convertView设置tag代码如下:
convertView.setTag(R.layout.item_expandablelistview_group,groupPosition);
convertView.setTag(R.layout.item_expandablelistview_child,-1);
由于点击分组的时候不涉及到子item这里我们给它设置为-1,用于在后面的长按事件中进行判断点击的是分组还是分组下的内容。
在长按事件的监听中获取当前点击项所对应的下标:
这里写代码片
expandableListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
Integer group = (Integer) view.getTag(R.layout.item_expandablelistview_group);
Integer child = (Integer)view.getTag(R.layout.item_expandablelistview_child);
//如果获取的child的值==-1说明点击的是分组
if(child == -1){
Toast.makeText(context,"你长按的是第"+group+"个分组",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(ExpandableListViewActivity.this,"group:"+group+" child:"+child,Toast.LENGTH_SHORT).show();
}
return false;
}
});
运行效果:
清明节假期最后一天继续窝在403机房。