中文官方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="" 他媽的竟然也沒法點擊