1、关于ExpandableListView的介绍
中文官方api--其实基本也不用怎么讲,直接看api也很清晰http://www.zhdoc.net/android/...
ExpandableListView 是默认支持二级展开树形结构,有的朋友喜欢用嵌套的方式实现多级的展开树,我并不建议那样用,写这篇文章就是单纯的总结一下这个空间,以及满足工作中只是简单的二级展开的需求。 后面我会再写一篇关于多层级的展开树,封装成自己的库使用。
2、ExpandableListView 使用
通过一个文件夹结构的例子来讲:
(1)创建布局文件,直接使用ExpandableListView
<?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">
<ExpandableListView
android:id="@+id/expand_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ExpandableListView>
</LinearLayout>
(2)创建ExpandableListView的适配器adapter
这里需要继承BaseExpandableListAdapter,然后实现它的方法
方法虽然挺多,但是好理解,看名字就能知道什么意思。这段代码只是个实例,下面贴出这个文件树结构的代码
public class ExpandListAdapter extends BaseExpandableListAdapter {
private Context mContext;
private List<Bean> folders; //文件夹数据
private List<List<Bean>> childData; //子文件数据
public ExpandListAdapter(Context context,
List<Bean> folders, List<List<Bean>> childData) {
this.mContext = context;
this.folders = folders;
this.childData = childData;
}
@Override
public int getGroupCount() {
return 0;
}
@Override
public int getChildrenCount(int groupPosition) {
return 0;
}
@Override
public Object getGroup(int groupPosition) {
return null;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return null;
}
@Override
public long getGroupId(int groupPosition) {
return 0;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
return null;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
return null;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
}
完整的代码如下:
public class ExpandListAdapter extends BaseExpandableListAdapter {
private Context mContext;
private List<Bean> folders; //文件夹数据
private List<List<Bean>> childData; //子文件数据
private SparseArray<ImageView> mIndicators; //创建一个map来存储指示器,然后根据位置改变
public ExpandListAdapter(Context context,
List<Bean> folders, List<List<Bean>> childData) {
this.mContext = context;
this.folders = folders;
this.childData = childData;
this.mIndicators=new SparseArray<>();
}
@Override //获取分组的个数(也就是这里的文件夹个数)
public int getGroupCount() {
return folders.size();
}
@Override//获取指定分组中子选项的个数
public int getChildrenCount(int groupPosition) {
return childData.get(groupPosition).size();
}
@Override//获取指定的分组数据
public Object getGroup(int groupPosition) {
return folders.get(groupPosition);
}
@Override //获取指定分组中指定子选项的数据
public Object getChild(int groupPosition, int childPosition) {
return childData.get(groupPosition).get(childPosition);
}
@Override//获取指定分组的ID, 这个ID必须是唯一的
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override//获取子选项的ID, 这个ID必须是唯一的
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override//分组和子选项是否持有稳定的ID, 就是说底层数据的改变会不会影响到它们。
public boolean hasStableIds() {
return true;
}
@Override//获取显示指定分组的视图
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
GroupViewHolder groupViewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.expadn_list_layout, parent, false);
groupViewHolder = new GroupViewHolder();
groupViewHolder.tv_title = convertView.findViewById(R.id.group_title);
groupViewHolder.iv_icon = convertView.findViewById(R.id.group_icon);
convertView.setTag(groupViewHolder);
}else{
groupViewHolder= (GroupViewHolder) convertView.getTag();
}
groupViewHolder.tv_title.setText(folders.get(groupPosition).getName());
//把要随着状态改变的imageView 指示器添加到集合里面
mIndicators.put(groupPosition,groupViewHolder.iv_icon);
//改变指示器展开或者关闭的显示
setIndicator(groupPosition,isExpanded);
return convertView;
}
@Override//获取显示指定分组中的指定子选项的视图
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
ChildViewHolder childViewHolder;
if (convertView==null){
convertView=LayoutInflater.from(mContext).inflate(R.layout.expand_child_layout,parent,false);
childViewHolder=new ChildViewHolder();
childViewHolder.tvTitle=convertView.findViewById(R.id.child_title);
convertView.setTag(childViewHolder);
}else{
childViewHolder= (ChildViewHolder) convertView.getTag();
}
childViewHolder.tvTitle.setText(childData.get(groupPosition).get(childPosition).getName());
return convertView;
}
@Override//指定位置上的子元素是否可选中
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
class GroupViewHolder {
TextView tv_title;
ImageView iv_icon;
}
class ChildViewHolder {
TextView tvTitle;
}
//设置展开收起的指示器
public void setIndicator(int position,boolean isExpanded){
if (isExpanded){
//从集合中取出指示器的imageview,改变图片的显示
mIndicators.get(position).setImageResource(R.mipmap.file_enter_icon);
}else{
mIndicators.get(position).setImageResource(R.mipmap.file_enter_icon_down);
}
}
}
代码很简单,这里讲一下过程
1、创建adapter类继承BaseExpandableListAdapter ,实现它的方法
2、根据传入的数据填充方法:这里传入了两个集合,一个是文件夹的集合,也就是这个树结构有多少个group;还有一个是元素是结合的集合,存储的是哪一个文件夹下的数据,也就是通过它来取出哪一组,然后从取出的组再取出组里的child
3、跟使用ListView一样,创建Viewholder,然后在getGroupView()和getChildView()这两个方法中找到相应的布局,声明相应的控件。然后优化一下性能方面,具体步骤看代码,和listView一模一样。
4、自定义指示器:(默认的实在是太丑了)
这个也很简单,就是上面文件夹布局(group)中添加一个imageview,具体添加在哪里,看个人喜好,我加载最右边了。通过展开、合上来动态修改显示的图片,待会再看代码,这里只是了解过程
5、为ExpandableListView设置适配器以及隐藏掉默认的指示器
ExpandListAdapter adapter=new ExpandListAdapter(this,folders,childData);
expandList.setGroupIndicator(null);//把指示器设为null
expandList.setAdapter(adapter); //设置adapter
6、这个是硬加的,别忘了要创造数据,这里造假数据。我是创建了一个Bean类
//初始化数据,由于没有后台接口,我们自己造假数据。开发中根据情况自己获取就可以
//模拟文件夹
private void initData() {
folders = new ArrayList<>();
folders.add(new Bean("文件夹一"));
folders.add(new Bean("文件夹二"));
folders.add(new Bean("文件夹三"));
folders.add(new Bean("文件夹四"));
childData = new ArrayList<>();
List<Bean> list = new ArrayList<>();
list.add(new Bean("A-01.1 Siteplan"));
list.add(new Bean("A-01.2 Basement"));
list.add(new Bean("A-01.3 楼层图"));
list.add(new Bean("A-01.4 First floor"));
childData.add(list);
List<Bean> list1 = new ArrayList<>();
list1.add(new Bean("A-02.1 图纸一"));
list1.add(new Bean("A-02.2 图纸二"));
childData.add(list1);
List<Bean> list2 = new ArrayList<>();
list2.add(new Bean("A-03.1 三楼图纸"));
list2.add(new Bean("A-03.2 floor"));
childData.add(list2);
List<Bean> list3 = new ArrayList<>();
list3.add(new Bean("A-04.1 pager1"));
list3.add(new Bean("A-04.2 pager2"));
list3.add(new Bean("A-04.3 pager3"));
list3.add(new Bean("A-04.4 pager4"));
childData.add(list3);
}
Bean类如下:
public class Bean {
private String name;
public Bean() {
}
public Bean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
通过以上的步骤我们基本上就已经实现了二级展开的树结构。
最后把没说完的讲完: 自定义指示器
private SparseArray<ImageView> mIndicators;
//创建一个map来存储指示器,然后根据位置改变
//其实就是在走getGroupView的方法时每次更新时把item的指示器存入,
//然后在根据位置取出来,根据当时的状态来动态改变图片
我是在构造方法中进行初始化的。这个无所谓。顺便贴一下把
public ExpandListAdapter(Context context,
List<Bean> folders, List<List<Bean>> childData) {
this.mContext = context;
this.folders = folders;
this.childData = childData;
this.mIndicators=new SparseArray<>();//初始化
}
然后创建一个方法,来根据位置和展开状态来动态更新图片
//设置展开收起的指示器
public void setIndicator(int position,boolean isExpanded){
if (isExpanded){
//从集合中取出指示器的imageview,改变图片的显示
mIndicators.get(position).setImageResource(R.mipmap.file_enter_icon);
}else{
mIndicators.get(position).setImageResource(R.mipmap.file_enter_icon_down);
}
}
最后千万别忘了一个最重要的操作:
在getGroupView方法中,要把imageView加到集合中,然后调用setIndicator()方法
那一块的代码我也贴一下:
@Override//获取显示指定分组的视图
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
GroupViewHolder groupViewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.expadn_list_layout, parent, false);
groupViewHolder = new GroupViewHolder();
groupViewHolder.tv_title = convertView.findViewById(R.id.group_title);
groupViewHolder.iv_icon = convertView.findViewById(R.id.group_icon);
convertView.setTag(groupViewHolder);
}else{
groupViewHolder= (GroupViewHolder) convertView.getTag();
}
groupViewHolder.tv_title.setText(folders.get(groupPosition).getName());
//把要随着状态改变的imageView 指示器添加到集合里面
mIndicators.put(groupPosition,groupViewHolder.iv_icon);
//改变指示器展开或者关闭的显示
setIndicator(groupPosition,isExpanded);
return convertView;
}
3、点击事件
对于处理 Item 的点击事件,还是要设置监听器,常用的有这几类:
setOnChildClickListener()
setOnGroupClickListener()
setOnGroupCollapseListener()
setOnGroupExpandListener()
它们分别设置单击子选项、单击分组项、分组合并、分组展开的监听器。
这个不讲了,具体方法怎么用,直接看顶部的api文档,中文的,啥都有
4、随便聊聊
关于展开、合并的指示器,可以把setIndicator方法的调用放在点击事件里面。
每次点击的时候去实现展开关闭的操作
如果重写点击事件方法,他是默认展开的。。如果我们把return false 变为return true,而不给他指定操作,他就不会展开了。。返回true应该是表示这个点击事件我来做处理,处理完了,但是什么没有做,所以要自己写操作
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView expandableListView, View view, int i, long l) {
boolean groupExpanded = expandableListView.isGroupExpanded(i);
if (groupExpanded) {
expandableListView.collapseGroup(i);
} else {
expandableListView.expandGroup(i, true);
}
adapter.setIndicatorState(i, !groupExpanded);
Log.d("测试", "点击事件方法调用了");
return true;
}
});
下面说的大家不用看,我只是自己记录下
补充一下:我写过一个文件夹的需求,文件夹的图片有四种,选中、不选中、展开并选中、展开不选中--
1、//创建一个集合存储组文件状态
private SparseArray<Boolean> expaned;
expaned=new SparseArray<>();
2、//在getGroupView 方法中把位置和状态存入
expaned.put(i,isExpanded);
3、在修改指示器的方法中加入判断
// 根据分组的展开闭合状态设置指示器
public void setIndicatorState(int groupPosition, boolean isExpanded) {
//先遍历用户操作,根据之前的状态改变指示器(比如:展开了,但没有选中)
for (int i = 0; i <mIndicators.size(); i++) {
if (expaned.get(i)) {
mIndicators.get(i).setImageResource(R.drawable.foder_open);
} else {
mIndicators.get(i).setImageResource(R.drawable.foder_icon);
}
}
//然后根据点击事件传过来的状态,改变当前item的指示器样式
if (isExpanded) {
mIndicators.get(groupPosition).setImageResource(R.drawable.foder_icon_open);
} else {
mIndicators.get(groupPosition).setImageResource(R.drawable.foder_icon);
}
}
这个方法在点击事件中调用。每次都会遍历一遍存入的集合,然后根据状态更新一下。选中不选中。展开不展开
后来使用的天坑 记录下----浪费了很长时间
首先都知道子布局中如果有抢焦点的控件,比如Button,ExpandablelistView是无法点击的
如果是TextView ,设置了 inputType="" 他妈的居然也无法点击
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。