能伸能縮的ExpandableListView(仿智聯招聘專業選擇列表頁面)

1、前言

前些日子項目中須要實現一個相似於智聯招聘的專業選擇頁面,簡單地說就是點擊一級專業列表中的某一項就會展開二級專業列表,一級列表就是一個個組(組選項),二級列表就是一個組裏面的成員(子選項)。智聯招聘的效果以下:html

智聯招聘-未展開樣式

智聯招聘-展開子列表

如今的主流列表控件毫無疑問是RecyclerView,因此你也許會想到用一個RecyclerView來顯示組列表,而後在其item裏面再嵌套一個RecyclerView顯示子選項列表。點擊組選項就將嵌套的RecyclerView佈局設爲visible或者gone來展開和關閉子列表。這種作法有以下的缺點:java

  1. RecyclerView的嵌套容易形成卡頓;
  2. 點擊組列表的最後一項時,雖然子列表已經顯示了,可是在屏幕以外,須要向上滑動才能看到,用戶體驗不是很好。

雖然RecyclerView是當紅炸子雞,可是解決這些問題仍是得老司機ExpandableListView出馬了。這是有點年頭的控件了,不過寶刀未老,咱們能夠用它輕鬆實現下拉列表效果。在這裏我不打算一一羅列ExpandableListView的用法,而是採起實戰的方式,以實現需求爲中心,用到哪一個再講那個。由於我以爲在實戰中學習和填坑更有趣味,更有效果。因此,下面咱們就一塊兒來作一個智聯招聘的專業選擇頁面吧。android

先提早看看咱們要實現的效果:git

專業列表選擇頁效果圖

2、佈局

2.1 主佈局

總體佈局很簡單,放一個ExpandableListView就能夠了:segmentfault

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
  >

    <ExpandableListView
        android:id="@+id/expandable_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

2.2 組選項的item佈局

組選項只須要顯示文字,因此先放一個TextView:數組

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:paddingLeft="20dp"
    android:gravity="center_vertical"
    android:background="@android:color/white"
    android:orientation="vertical">

    <TextView
        android:text="dd"
        android:gravity="center_vertical"
        android:id="@+id/tv_group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="16sp" />

</LinearLayout>

2.3 子選項的item佈局

子選項的item佈局與組選項的惟一區別就是它的背景是灰色的(android:background="#F2F2F2"),代碼就不重複貼了。app

3、數據準備

一個組選項對應的是一組子選項,因此組選項的數據是一個一級數組,子選項的數據是二級數組。爲了添加數據方便,我這裏使用的是集合,每一組的子選項數據個數設爲隨機:dom

private void initData(){
        //初始化一級專業數據
        for (int i = 1; i <= 15; i++) {
            groupList.add(new StringBuffer("一級專業").append(i).toString());
        }
        //初始化二級專業數據
        Random random = new Random();
        for (String s : groupList) {
            List<String> childDatas = new ArrayList<>();
            int size = random.nextInt(10) + 5;
            for (int i = 1; i <= size; i++) {
                childDatas.add(new StringBuffer("二級專業").append(i).toString());
            }
            childList.add(childDatas);
        }
    }

運行以後發現數據都有了,可是見鬼,爲何組選項和子選項的高度都那麼窄呢?ide

4、選項高度爲wrap_content的坑

這能夠算是ExpandableListView的一個小坑,當組選項或者子選項的根佈局高度設置爲固定值,實際出來的效果倒是wrap_content。解決這個問題能夠給根佈局再加一個屬性android:minHeight="50dp",或者也給子控件TextView加上固定的高度(若是你的item裏面的控件比較簡單能夠採起這個方法)。固然,若是你的item高度不是一個固定值,也能夠將高度設爲wrap_content,而後在裏面設置padding或者margin值,好比:佈局

android:paddingTop="10dp"
        android:paddingBottom="10dp"

你能夠根據需求採起合適的方案。

5、修改組選項的指示器

仔細觀察咱們能夠發現,ExpandableListView默認顯示了一個指示器,也就是左邊的小箭頭,而且提供了屬性android:groupIndicator來設置其樣式。這個指示器是固定在左邊的,雖然能夠設置上下左右的距離,可是很難控制,一不當心就會跟文字重疊,因此我作法是乾脆設置android:groupIndicator="@null"讓其消失,而後本身在組選項的佈局中用兩張圖片來替代。

組選項的佈局修改以下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@android:color/white"
    android:gravity="center_vertical"
    android:minHeight="50dp"
    android:orientation="vertical"
    android:paddingLeft="20dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_toLeftOf="@+id/iv_indicator"
            android:layout_centerVertical="true"
            android:id="@+id/tv_group"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:textColor="@android:color/black"
            android:textSize="16sp" />

        <ImageView
            android:layout_marginRight="20dp"
            android:id="@+id/iv_indicator"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </RelativeLayout>
</LinearLayout>

回到MajorAdapter,在getGroupView方法中有一個isExpanded參數,它表示的是組選項的展開狀態,true時表示展開,false則是閉合。有了它,咱們就能夠輕鬆決定箭頭指示器的方向了。

@Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        ……
        //根據列表的展開狀態來決定箭頭的方向
        groupHolder.ivIndicator.setImageResource(isExpanded ?
                R.drawable.ic_arrow_up : R.drawable.ic_arrow_down);
        return convertView;
    }

6、修改分割線樣式

ExpandableListView是繼承於ListView,因此它也能夠經過android:divider屬性來同時設置組選項和子選項的分割線。咱們須要的分割線左邊有20dp的間距,因此只好設置android:divider="@null"來去掉原生的分割線,而且本身寫一條了。

建立一個layout_divider.xml的佈局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#d4d4d4"
    android:layout_height="0.5dp"
    >
</RelativeLayout>

而後在組選項和子選項的佈局中include進去:

<include layout="@layout/layout_divider" />

至此,咱們的界面就已經完成了,如今來實現它的點擊事件吧。

7、組選項和子選項的點擊事件

ExpandableListView提供了setOnGroupClickListenersetOnChildClickListener兩個方法來分別監聽組選項和子選項的點擊事件。好比監聽子選項的點擊事件:

//子選項的點擊事件
        expandableList.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
                                        int childPosition, long id) {
                String toastStr = new StringBuffer("你選擇了").append(groupList.get(groupPosition))
                        .append("的").append(childList.get(groupPosition).get(childPosition)).toString();
                Toast.makeText(context, toastStr, Toast.LENGTH_SHORT).show();
                return false;
            }
        });

組選項的點擊監聽事件類似,這裏就再也不贅述了。

8、組選項的展開監聽事件

如今咱們來實現一個智聯招聘中沒有的功能吧,即點擊某一組選項時,被點擊的組選項展開,其餘展開的組選項自動關閉,也就是每次只能有一個組選項展開子列表。你也許會想到用上一節中的setOnGroupClickListener,可是這個是每次組選項被點擊時都會監聽,哪怕是已經關閉了,因此性能上會有點浪費。除此以外,咱們還有更好的選擇,那就是使用setOnGroupExpandListener監聽列表的展開事件:

//實現每次只展開一個組選項列表的功能
        expandableList.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
            @Override
            public void onGroupExpand(int groupPosition) {
                //獲取組選項個數
                int groupSize = expandableList.getExpandableListAdapter().getGroupCount();
                for (int i = 0; i < groupSize; i++) {
                    if (i != groupPosition && expandableList.isGroupExpanded(i)) {
                        //不是當前點擊的組選項且處於展開狀態的就關閉
                        expandableList.collapseGroup(i);
                    }
                }
            }
        });

每次點擊展開某個組選項時,咱們就遍歷組選項列表,比較它們的groupPosition,若是不是當前點擊的組選項且處於展開狀態,就調用collapseGroup將其關閉。

你必定會想到,既然有方法能夠將某一組選項裏面的列表關閉,是否是也有對應的方法展開呢?沒錯,相對於collapseGroup,還有對應的expandGroup方法,並且它的第二個參數能夠設置展開時是否顯示動畫效果。若是你想頁面顯示時就展開某一特定的子列表,那麼就可使用expandGroup了。

9、總結

至此,咱們的界面和功能都已經實現完畢了。如今就來梳理用到的屬性和API吧。

首先是xml屬性:

屬性 做用
android:groupIndicator 設置組選項的指示器
android:divider 設置組選項和子選項列表的分割線

而後是用到的方法:

方法 做用
setOnGroupExpandListener 組選項的點擊監聽事件
setOnChildClickListener 子選項的點擊監聽事件
setOnGroupExpandListener 組選項的展開監聽事件
getExpandableListAdapter 獲取ExpandableListView綁定的Adapter
collapseGroup 關閉某一組選項下的列表
expandGroup 展開某一組選項下的列表

若是你還想深刻學習ExpandableListView,能夠閱讀這份官方文檔:
http://www.android-doc.com/reference/android/widget/ExpandableListView.html#getFlatListPosition(long))

10、源碼提供

源碼我放作了碼雲上,可是因爲裏面還有個人一些亂七八糟的代碼,因此不建議你們把整個工程下載下來,只需關注ExpandableListView包下面這幾個文件就好了:

  • ExpandableListViewActivity
  • MajorFragment
  • MajorAdapter

我也將本項目的代碼和資源文件打包上傳到了百度網盤,你能夠直接下載使用:
百度網盤

事實上,這裏代碼難度不大,因此我強烈建議你親自動手敲一遍。最後,祝你們學習愉快。

相關文章
相關標籤/搜索