1. 程式人生 > >Android中ExpandableListView的使用(一)

Android中ExpandableListView的使用(一)

相關文章:

ExpandableListView是可擴充套件的下拉列表,它的可擴充套件性在於點選父item可以拉下或收起列表,適用於一些場景的使用,下面介紹的是在Activity中如何使用,關於它的各種樣式的詳細解釋請見另一篇文章:Android中ExpandableListView常用屬性總結

下面介紹它的基本使用方法

先看一下效果:


一、最基本的使用

新建一個佈局檔案expandable_layout.xml,內容很簡單,一個LinearLayout裡面包含了一個ExpandableListView,別忘了給它加上id:

<?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/expandablelistview"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

然後在activity檔案中引入這個layout,獲取這個ExpandableListView:

private ExpandableListView listview;
setContentView(R.layout.expandable_layout);
listview = (ExpandableListView) findViewById(R.id.expandablelistview);
為了給ExpandableListView提供資料,需要先初始化資料,這裡使用一個Map來存放資料,型別為<String, List<String>>:
private Map<String, List<String>> dataset = new HashMap<>();
private String[] parentList = new String[]{"first", "second", "third"};
private List<String> childrenList1 = new ArrayList<>();
private List<String> childrenList2 = new ArrayList<>();
private List<String> childrenList3 = new ArrayList<>();
private void initialData() {
    childrenList1.add(parentList[0] + "-" + "first");
    childrenList1.add(parentList[0] + "-" + "second");
    childrenList1.add(parentList[0] + "-" + "third");
    childrenList2.add(parentList[1] + "-" + "first");
    childrenList2.add(parentList[1] + "-" + "second");
    childrenList2.add(parentList[1] + "-" + "third");
    childrenList3.add(parentList[2] + "-" + "first");
    childrenList3.add(parentList[2] + "-" + "second");
    childrenList3.add(parentList[2] + "-" + "third");
    dataset.put(parentList[0], childrenList1);
    dataset.put(parentList[1], childrenList2);
    dataset.put(parentList[2], childrenList3);
}
然後需要自己實現一個Adapte類,用於為ExpandableListView提供資料,該類繼承了BaseExpandableListAdapter,下面這個是最簡單的自定義的類:
private class MyExpandableListViewAdapter extends BaseExpandableListAdapter {

    //  獲得某個父項的某個子項
    @Override
    public Object getChild(int parentPos, int childPos) {
        return dataset.get(parentList[parentPos]).get(childPos);
    }

    //  獲得父項的數量
    @Override
    public int getGroupCount() {
        return dataset.size();
    }

    //  獲得某個父項的子項數目
    @Override
    public int getChildrenCount(int parentPos) {
        return dataset.get(parentList[parentPos]).size();
    }

    //  獲得某個父項
    @Override
    public Object getGroup(int parentPos) {
        return dataset.get(parentList[parentPos]);
    }

    //  獲得某個父項的id
    @Override
    public long getGroupId(int parentPos) {
        return parentPos;
    }

    //  獲得某個父項的某個子項的id
    @Override
    public long getChildId(int parentPos, int childPos) {
        return childPos;
    }

    //  按函式的名字來理解應該是是否具有穩定的id,這個方法目前一直都是返回false,沒有去改動過
    @Override
    public boolean hasStableIds() {
        return false;
    }

    //  獲得父項顯示的view
    @Override
    public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {
        return view;
    }

    //  獲得子項顯示的view
    @Override
    public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {
        return view;
    }

    //  子項是否可選中,如果需要設定子項的點選事件,需要返回true
    @Override
    public boolean isChildSelectable(int i, int i1) {
        return false;
    }
 }

其中註釋說明了每個方法的作用,這個adapter的所有資料來源都是剛剛初始化的dataset,因此當這個adapter需要返回父項的數目時,返回的就是dataset的大小,如果需要返回某個父項的某個子項時,通過父項的position,使用map的get方法即可獲得。自定義的類中最重要的是下面這兩個方法,下面先介紹getGroupView方法:

        //  獲得父項顯示的view
        @Override
        public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.parent_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, -1);
            TextView text = (TextView) view.findViewById(R.id.parent_title);
            text.setText(parentList[parentPos]);
            return view;
        }
這個方法用來指定父項顯示的樣式,內容和行為,其中使用了parent_item佈局:
<?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">
    <TextView
        android:id="@+id/parent_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:text="這是父item"
        android:layout_margin="5dp"/>
</LinearLayout>
佈局很簡單,只是一個LinearLayout包含了一個TextView。

getGroupView方法先判斷view是否為空,如果view不為空,說明已經載入過一次parent_item佈局,因此不需要重複載入以提高效率。如果view為空,那麼使用如下方法載入parent_item佈局:

if (view == null) {
    LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                   .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    view = inflater.inflate(R.layout.parent_item, null);
}
然後定義父項的內容和行為,這裡只需要定義父項的內容,先通過id獲得parent_item佈局中的TextView,然後通過方法提供的parentPos引數獲取到存放在dataset中的內容,呼叫TextView的setText方法即可設定父項要顯示的內容:
TextView text = (TextView) view.findViewById(R.id.parent_title);
text.setText(parentList[parentPos]);

然後將view返回即可。

接下來是getChildView方法:

        //  獲得子項顯示的view
        @Override
        public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.child_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, childPos);
            TextView text = (TextView) view.findViewById(R.id.child_title);
            text.setText(dataset.get(parentList[parentPos]).get(childPos));
            text.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(ExpandableListViewTestActivity.this, "點到了內建的textview", Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
這個方法用於定義子項的佈局,內容和行為,同樣需要一個child_item來指定子項的佈局:
<?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">
    <TextView
        android:id="@+id/child_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="@color/black"
        android:text="這是子item"
        android:layout_margin="5dp"/>
</LinearLayout>
關於view的處理和父項一樣,這裡在方法中還給子項顯示內容的textview添加了一個點選的監聽器。

adapter自定義完之後,通過宣告一個Adapter,例項化後呼叫setAdapter方法即可:

private MyExpandableListViewAdapter adapter;
adapter = new MyExpandableListViewAdapter();
listview.setAdapter(adapter);
至此,最基本的ExpandableListView的使用就可以滿足啦

二、稍微複雜一點的使用方法

1、為子項新增點選的監聽事件,效果圖:


只需要呼叫ExpandableListView的setOnChildClickListener方法即可:

        listview.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView expandableListView, View view,
                                        int parentPos, int childPos, long l) {
                Toast.makeText(ExpandableListViewTestActivity.this,
                        dataset.get(parentList[parentPos]).get(childPos), Toast.LENGTH_SHORT).show();
                return true;
            }
        });
這裡在每個子項被點選了之後會顯示是哪個子項被點選了
特別注意
(1)在使用這個方法的時候需要將自定義的adapter中的isChildSelectable方法的返回值設定為true,否則子項的點選不生效,但子項佈局中設定的控制元件的監聽器依然可以生效。
        //  子項是否可選中,如果需要設定子項的點選事件,需要返回true
        @Override
        public boolean isChildSelectable(int i, int i1) {
            return true;
        }
(2)如果在子項中對某個控制元件設定了監聽器,這個控制元件要注意不能鋪滿整個子項,所以在設定高度和寬度時要特別注意,否則設定了子項的監聽器也是沒有用的
2、為子項新增長按的監聽器
在ExpandableListView中並沒有提供設定子項長按監聽器的方法,多方查詢之後找到了一個算是比較靠譜的方法,目前用起來暫時還沒有什麼問題,有問題再來更新這篇文章。
效果圖:
ExpandableListView中關於長按有一個setOnItemLongClickListener方法,但是這個方法有個問題,沒辦法區分被長按的item是父項還是子項,所以需要在自定義adapter的getGroupView方法和getChildView方法中加一點東西來區分是父項還是子項:
完整的getGroupView方法和getChildView方法:
        //  獲得父項顯示的view
        @Override
        public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.parent_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, -1);
            TextView text = (TextView) view.findViewById(R.id.parent_title);
            text.setText(parentList[parentPos]);
            return view;
        }

        //  獲得子項顯示的view
        @Override
        public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.child_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, childPos);
            TextView text = (TextView) view.findViewById(R.id.child_title);
            text.setText(dataset.get(parentList[parentPos]).get(childPos));
            text.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(ExpandableListViewTestActivity.this, "點到了內建的textview",
                            Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
這裡用到了view的setTag方法,一共設定了兩個Tag,標籤雖然在設定的時候提示說只要int型別即可,但一開始使用0和1來做tag的時候,顯示沒有報錯,但編譯執行就報錯了,要求是資原始檔的id才行,因此換成了R.layout.parent_item和R.layout.child_item。
如果是父項,就設定R.layout.parent_item為第幾個父項,設定R.layout.child_item為-1。如果是子項,就設定R.layout.parent_item屬於第幾個父項,設定R.layout.child_item為該父項的第幾個子項,這樣就可以區分被長按的是父項還是子項了。
然後設定ExpandableListView長按item的監聽器:
listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
                String content = "";
                if ((int) view.getTag(R.layout.child_item) == -1) {
                    content = "父類第" + view.getTag(R.layout.parent_item) + "項" + "被長按了";
                } else {
                    content = "父類第" + view.getTag(R.layout.parent_item) + "項" + "中的"
                            + "子類第" + view.getTag(R.layout.child_item) + "項" + "被長按了";
                }
                Toast.makeText(ExpandableListViewTestActivity.this, content, Toast.LENGTH_SHORT).show();
                return true;
            }
 });
這樣就可以區分父項和子項了。
3、更新資料
列表項更新資料一般使用的都是adapter的notifyDataSetChanged()方法,ExpandableListView也不例外,下面展示一下怎麼更新資料。
效果圖:
首先要在原先的expandable_layout.xml檔案中新增一個按鈕,用來更新資料:
<?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/expandablelistview"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/updateData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:layout_gravity="center"
        android:text="重新整理資料"/>
</LinearLayout>
然後在Activity檔案中引入這個Button:
private Button button;
button = (Button) findViewById(R.id.updateData);
為button設定點選的監聽器:
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateData();
                Toast.makeText(ExpandableListViewTestActivity.this, "資料已更新", Toast.LENGTH_SHORT).show();
            }
        });
其中updateData()方法就是用於更新ExpandableListView的方法,看一下具體的實現:
    /**
     * 更新資料
     */
    private void updateData() {
        childrenList1.clear();
        childrenList1.add(parentList[0] + "-new-" + "first");
        childrenList1.add(parentList[0] + "-new-" + "second");
        childrenList1.add(parentList[0] + "-new-" + "third");
        childrenList2.clear();
        childrenList2.add(parentList[1] + "-new-" + "first");
        childrenList2.add(parentList[1] + "-new-" + "second");
        childrenList2.add(parentList[1] + "-new-" + "third");
        childrenList3.clear();
        childrenList3.add(parentList[2] + "-new-" + "first");
        childrenList3.add(parentList[2] + "-new-" + "second");
        childrenList3.add(parentList[2] + "-new-" + "third");
        adapter.notifyDataSetChanged();
    }
還記得上面初始化資料的時候使用的childrenList1、childrenList2、childrenList3嗎,這三個列表是我們用來放子項列表,然後已經新增到dataset裡面去了。這個時候無需再用dataset新增一次,只需要將列表清空,新增上更新的內容即可。也可以根據自己的需求選擇性地刪除一些東西來獲得新的子項列表。
需要注意的是,子項列表在記憶體中的地址是不可以改變的,不能使用形如childrenList1 = new ArrayList<>();這樣的方法來獲的新列表,這樣會使childrenList1在記憶體中的地址發生改變,導致呼叫adapter的notifyDataSetChanged()方法時不生效。所以如果想要清空這個列表項時,使用childrenList1.clear()方法,這樣才能保證順利更新列表。
文章中使用的程式碼原始碼已放上github:https://github.com/sysukehan/AndroidTests.git,存放在ExpandableListViewTest模組下
關於ExpandableListView的使用方法先說到這裡,以後如果有問題或者有東西要補充再更新這篇文章