中文官方api--其实基本也不用怎么讲,直接看api也很清晰http://www.zhdoc.net/android/...html
ExpandableListView 是默认支持二级展开树形结构,有的朋友喜欢用嵌套的方式实现多级的展开树,我并不建议那样用,写这篇文章就是单纯的总结一下这个空间,以及知足工做中只是简单的二级展开的需求。 后面我会再写一篇关于多层级的展开树,封装成本身的库使用。
经过一个文件夹结构的例子来说:
(1)建立布局文件,直接使用ExpandableListViewandroid
<?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,而后实现它的方法
方法虽然挺多,可是好理解,看名字就能知道什么意思。这段代码只是个实例,下面贴出这个文件树结构的代码api
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; } }
完整的代码以下:ide
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); } } }
代码很简单,这里讲一下过程
一、建立adapter类继承BaseExpandableListAdapter ,实现它的方法
二、根据传入的数据填充方法:这里传入了两个集合,一个是文件夹的集合,也就是这个树结构有多少个group;还有一个是元素是结合的集合,存储的是哪个文件夹下的数据,也就是经过它来取出哪一组,而后从取出的组再取出组里的child
三、跟使用ListView同样,建立Viewholder,而后在getGroupView()和getChildView()这两个方法中找到相应的布局,声明相应的控件。而后优化一下性能方面,具体步骤看代码,和listView如出一辙。
四、自定义指示器:(默认的实在是太丑了)
这个也很简单,就是上面文件夹布局(group)中添加一个imageview,具体添加在哪里,看我的喜爱,我加载最右边了。经过展开、合上来动态修改显示的图片,待会再看代码,这里只是了解过程
五、为ExpandableListView设置适配器以及隐藏掉默认的指示器布局
ExpandListAdapter adapter=new ExpandListAdapter(this,folders,childData); expandList.setGroupIndicator(null);//把指示器设为null expandList.setAdapter(adapter); //设置adapter
六、这个是硬加的,别忘了要创造数据,这里造假数据。我是建立了一个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; } }
经过以上的步骤咱们基本上就已经实现了二级展开的树结构。优化
最后把没说完的讲完: 自定义指示器this
private SparseArray<ImageView> mIndicators; //建立一个map来存储指示器,而后根据位置改变 //其实就是在走getGroupView的方法时每次更新时把item的指示器存入, //而后在根据位置取出来,根据当时的状态来动态改变图片
我是在构造方法中进行初始化的。这个无所谓。顺便贴一下把.net
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; }
对于处理 Item 的点击事件,仍是要设置监听器,经常使用的有这几类:
setOnChildClickListener()
setOnGroupClickListener()
setOnGroupCollapseListener()
setOnGroupExpandListener()
它们分别设置单击子选项、单击分组项、分组合并、分组展开的监听器。
这个不讲了,具体方法怎么用,直接看顶部的api文档,中文的,啥都有
关于展开、合并的指示器,能够把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; } });
下面说的你们不用看,我只是本身记录下
补充一下:我写过一个文件夹的需求,文件夹的图片有四种,选中、不选中、展开并选中、展开不选中--
一、//建立一个集合存储组文件状态 private SparseArray<Boolean> expaned; expaned=new SparseArray<>(); 二、//在getGroupView 方法中把位置和状态存入 expaned.put(i,isExpanded); 三、在修改指示器的方法中加入判断 // 根据分组的展开闭合状态设置指示器 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="" 他妈的竟然也没法点击