Android 的ExpandableListView使用總結--二級展開樹結構

一、關於ExpandableListView的介紹

中文官方api--其實基本也不用怎麼講,直接看api也很清晰http://www.zhdoc.net/android/...html

ExpandableListView 是默認支持二級展開樹形結構,有的朋友喜歡用嵌套的方式實現多級的展開樹,我並不建議那樣用,寫這篇文章就是單純的總結一下這個空間,以及知足工做中只是簡單的二級展開的需求。 後面我會再寫一篇關於多層級的展開樹,封裝成本身的庫使用。

二、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="" 他媽的竟然也沒法點擊

相關文章
相關標籤/搜索