Android開發之ExpandableListView

remotesupport發表於2014-07-29


有時候,使用ListView並不能滿足應用程式所需要的功能。有些應用程式需要多組ListView,這時候我們就要使用一種新的控制元件ExpandableListView——可以擴充套件的ListView。它的作用就是將ListView進行分組。就好像我們使用QQ的時候,有“我的好友”,“陌生人”,“黑名單”一樣,點選一下會擴充套件開,再點選一下又會收縮回去。

ExpandableListView是一個垂直滾動顯示兩級列表項的檢視,與ListView不同的是,它可以有兩層:每一層都能夠被獨立的展開並顯示其子項。這些子項來自於與該檢視關聯的ExpandableListAdapter。

每一個可以擴充套件的列表項的旁邊都有一個指示符(箭頭)用來說明該列表專案前的狀態(這些狀態一般是已經擴充套件開的列表項,還沒有擴充套件開的列表項,子列表項和最後一個子列表項)。可以使用方法:setChildIndicator(Drawable),setGroupIndicator(Drawable)(或者相應的XML檔案的屬性) 去設定這些指示符的樣式。當然也可以使用預設的指示符。布android.R.layout.simple_expandable_list_item_1,android.R.layout.simple_expandable_list_item_2

和ListView一樣,ExpandableListView也是一個需要Adapter作為橋樑來取得資料的控制元件。一般適用於ExpandableListView的Adapter都要繼承BaseExpandableListAdapter這個類,並且必須過載getGroupView和getChildView這兩個最為重要的方法。

BaseExpandableListAdapter的主要過載方法如下:

public abstract ObjectgetChild (int groupPosition, int childPosition)

取得與指定分組、指定子專案關聯的資料.

引數

groupPosition 包含子檢視的分組的位置.

childPosition   指定的分組中的子檢視的位置.

返回

與子檢視關聯的資料.

public abstract long getChildId (int groupPosition, intchildPosition)

取得給定分組中給定子檢視的ID. 該組ID必須在組中是唯一的.必須不同於其他所有ID(分組及子專案的ID).

引數

groupPosition 包含子檢視的分組的位置.

childPosition   要取得ID的指定的分組中的子檢視的位置.

返回

與子檢視關聯的ID.

public abstract View getChildView (int groupPosition, intchildPosition, boolean isLastChild, View convertView, ViewGroup parent)

取得顯示給定分組給定子位置的資料用的檢視.

引數

groupPosition 包含要取得子檢視的分組位置.

childPosition   分組中子檢視(要返回的檢視)的位置.

isLastChild     該檢視是否為組中的最後一個檢視.

convertView   如果可能,重用舊的檢視物件.使用前你應該保證檢視物件為非空,並且是否是合適的型別.如果該物件不能轉換為可以正確顯示資料的檢視,該方法就建立新檢視.不保證使用先前由 getChildView(int, int,boolean, View, ViewGroup)建立的檢視.

parent     該檢視最終從屬的父檢視.

返回

指定位置相應的子檢視.

public abstract int getChildrenCount (int groupPosition)

取得指定分組的子元素數.

引數

groupPosition 要取得子元素個數的分組位置.

返回

指定分組的子元素個數.

public abstract long getCombinedChildId (long groupId, long childId)

取得一覽中可以唯一識別子條目的 ID(包括分組ID和子條目ID).可擴充套件列表要求每個條目 (分組條目和子條目)具有一個可以唯一識別列表中子條目和分組條目的ID. 該方法根據給定子條目ID和分組條目ID返回唯一識別ID.另外,如果 hasStableIds() 為真,該函式返回的ID必須是固定不變的.

引數

groupId   包含子條目ID的分組條目ID.

childId    子條目的ID.

返回

可以在所有分組條目和子條目中唯一識別該子條目的ID(可能是固定不變的).

public abstract long getCombinedGroupId (long groupId)

取得一覽中可以唯一識別子條目的 ID(包括分組ID和子條目ID).可擴充套件列表要求每個條目 (分組條目和子條目)具有一個可以唯一識別列表中子條目和分組條目的ID. 該方法根據給定子條目ID和分組條目ID返回唯一識別ID.另外,如果 hasStableIds() 為真,該函式返回的ID必須是固定不變的.

引數

groupId   分組條目ID.

返回

可以在所有分組條目和子條目中唯一識別該分組條目的ID(可能是固定不變的).

public abstract Object getGroup (int groupPosition)

取得與給定分組關聯的資料.

引數

groupPosition 分組的位置.

返回

指定分組的資料.

public abstract int getGroupCount ()

取得分組數.

返回

分組數.

public abstract long getGroupId (int groupPosition)

取得指定分組的ID.該組ID必須在組中是唯一的.必須不同於其他所有ID(分組及子專案的ID).

引數

groupPosition 要取得ID的分組位置.

返回

與分組關聯的ID.

public abstract View getGroupView (int groupPosition, booleanisExpanded, View convertView, ViewGroup parent)

取得用於顯示給定分組的檢視. 這個方法僅返回分組的檢視物件, 要想獲取子元素的檢視物件,就需要呼叫 getChildView(int, int, boolean, View, ViewGroup).

引數

groupPosition 決定返回哪個檢視的組位置 .

isExpanded     該組是展開狀態還是收起狀態 .

convertView   如果可能,重用舊的檢視物件.使用前你應該保證檢視物件為非空,並且是否是合適的型別.如果該物件不能轉換為可以正確顯示資料的檢視,該方法就建立新檢視.不保證使用先前由 getGroupView(int, boolean,View, ViewGroup)建立的檢視.

parent     該檢視最終從屬的父檢視.

返回

指定位置相應的組檢視.

public abstract boolean hasStableIds ()

是否指定分組檢視及其子檢視的ID對應的後臺資料改變也會保持該ID.

返回

是否相同的ID總是指向同一個物件.

public abstract boolean isChildSelectable (int groupPosition, intchildPosition)

指定位置的子檢視是否可選擇.

引數

groupPosition 包含要取得子檢視的分組位置.

childPosition   分組中子檢視的位置.

返回

是否子檢視可選擇.

注意:

在XML佈局檔案中,如果ExpandableListView上一級檢視的大小沒有嚴格定義的話,則不能對ExpandableListView的android:layout_height 屬性使用wrap_content值。 (例如,如果上一級檢視是ScrollView的話,則不應該指定wrap_content的值,因為它可以是任意的長度。不過,如果ExpandableListView的上一級檢視有特定的大小的話,比如100畫素,則可以使用wrap_content)

如果由於開發的時候粗心,對ExpandableListView指定wrap_content的值,則會報一個在SetContentView處的空指標錯誤。

根據描述,先看一個簡單的例子:

定義XML的程式碼如下:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.     <ExpandableListView     
  7.         android:id = "@+id/myExpandableListView"  
  8.         android:layout_width="fill_parent"                    
  9.         android:layout_height="fill_parent"                  
  10.         android:layout_weight="1"                   
  11.         />     
  12. </LinearLayout>  

就是在LinearLayout中佈置了一個ExpandableListView。

在JAVA中的程式碼如下:

[java] view plaincopy
  1. /* 
  2.  *  Android開發之ExpandableListView 
  3.  *  北京Android俱樂部群:167839253 
  4. *  Created on: 2012-7-23 
  5.  *  ExpandableListViewActivity.java 
  6.  *  Author: blueeagle 
  7.  *  Email: liujiaxiang@gmail.com 
  8.  */  
  9.   
  10. package com.blueeagle.www;  
  11.   
  12. import java.util.ArrayList;  
  13. import java.util.List;  
  14. import android.app.Activity;  
  15. import android.os.Bundle;  
  16. import android.view.Gravity;  
  17. import android.view.View;  
  18. import android.view.ViewGroup;  
  19. import android.widget.AbsListView;  
  20. import android.widget.BaseExpandableListAdapter;  
  21. import android.widget.ExpandableListView;  
  22. import android.widget.TextView;  
  23.   
  24. public class ExpandableListViewActivity extends Activity {  
  25.     private List<String> GroupData;//定義組資料    
  26.     private List<List<String>> ChildrenData;//定義組中的子資料  
  27.     private void LoadListDate() {    
  28.         GroupData = new ArrayList<String>();    
  29.         GroupData.add("國家");    
  30.         GroupData.add("人物");    
  31.         GroupData.add("武器");    
  32.     
  33.         ChildrenData = new ArrayList<List<String>>();    
  34.         List<String> Child1 = new ArrayList<String>();    
  35.         Child1.add("蜀國");    
  36.         Child1.add("魏國");  
  37.         Child1.add("吳國");  
  38.         ChildrenData.add(Child1);    
  39.         List<String> Child2 = new ArrayList<String>();    
  40.         Child2.add("關羽");    
  41.         Child2.add("張飛");    
  42.         Child2.add("典韋");    
  43.         Child2.add("呂布");  
  44.         Child2.add("曹操");  
  45.         Child2.add("甘寧");  
  46.         Child2.add("郭嘉");  
  47.         Child2.add("周瑜");  
  48.         ChildrenData.add(Child2);    
  49.         List<String> Child3 = new ArrayList<String>();    
  50.         Child3.add("青龍偃月刀");    
  51.         Child3.add("丈八蛇矛槍");    
  52.         Child3.add("青鋼劍");    
  53.         Child3.add("麒麟弓");    
  54.         Child3.add("銀月槍");    
  55.         ChildrenData.add(Child3);    
  56.     }    
  57.     
  58.     @Override    
  59.     public void onCreate(Bundle savedInstanceState) {    
  60.         super.onCreate(savedInstanceState);    
  61.         setContentView(R.layout.main);    
  62.             
  63.         LoadListDate();    
  64.             
  65.         ExpandableListView myExpandableListView = (ExpandableListView)findViewById(R.id.myExpandableListView);    
  66.         myExpandableListView.setAdapter(new ExpandableAdapter());    
  67.     }    
  68.         
  69.     private class ExpandableAdapter extends BaseExpandableListAdapter {    
  70.         @Override  
  71.         public Object getChild(int groupPosition, int childPosition) {  
  72.             return ChildrenData.get(groupPosition).get(childPosition);  
  73.         }  
  74.   
  75.         @Override  
  76.         public long getChildId(int groupPosition, int childPosition) {  
  77.             return 0;  
  78.         }  
  79.   
  80.         @Override  
  81.         public View getChildView(int groupPosition, int childPosition,  
  82.                 boolean isLastChild, View convertView, ViewGroup parent) {  
  83.             TextView myText = null;    
  84.             if (convertView != null) {    
  85.                 myText = (TextView)convertView;    
  86.                 myText.setText(ChildrenData.get(groupPosition).get(childPosition));    
  87.             } else {    
  88.                 myText = createView(ChildrenData.get(groupPosition).get(childPosition));    
  89.             }    
  90.             return myText;    
  91.         }  
  92.   
  93.         @Override  
  94.         public int getChildrenCount(int groupPosition) {  
  95.             return ChildrenData.get(groupPosition).size();  
  96.         }  
  97.   
  98.         @Override  
  99.         public Object getGroup(int groupPosition) {  
  100.             return GroupData.get(groupPosition);  
  101.         }  
  102.   
  103.         @Override  
  104.         public int getGroupCount() {  
  105.             return GroupData.size();  
  106.         }  
  107.   
  108.         @Override  
  109.         public long getGroupId(int groupPosition) {  
  110.             return 0;  
  111.         }  
  112.   
  113.         @Override  
  114.         public View getGroupView(int groupPosition, boolean isExpanded,  
  115.                 View convertView, ViewGroup parent) {  
  116.             TextView myText = null;    
  117.             if (convertView != null) {    
  118.                 myText = (TextView)convertView;    
  119.                 myText.setText(GroupData.get(groupPosition));    
  120.             } else {    
  121.                 myText = createView(GroupData.get(groupPosition));    
  122.             }    
  123.             return myText;  
  124.         }  
  125.   
  126.         @Override  
  127.         public boolean hasStableIds() {  
  128.             return false;  
  129.         }  
  130.   
  131.         @Override  
  132.         public boolean isChildSelectable(int groupPosition, int childPosition) {  
  133.             return false;  
  134.         }    
  135.         private TextView createView(String content) {    
  136.             AbsListView.LayoutParams layoutParams = new AbsListView.LayoutParams(      
  137.                     ViewGroup.LayoutParams.FILL_PARENT, 80);      
  138.             TextView myText = new TextView(ExpandableListViewActivity.this);      
  139.             myText.setLayoutParams(layoutParams);      
  140.             myText.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);      
  141.             myText.setPadding(80000);      
  142.             myText.setText(content);    
  143.             return myText;    
  144.         }  
  145.     }    
  146. }  

效果如下圖所示:



這樣就完成了一個最簡單的ExpandableListView的控制元件。

       在實際開發過程中,常常有不同的需求,比如每一個child需要不同的控制元件,每一個group或者child需要有圖示,圖示顯示需要不一樣,需要設定背景等各種各樣能夠讓我們的程式變得美觀的需求。那麼下面就逐一討論一下ExpandableListView如何實現這些需求。

       比如,為ExpandableListView設定背景,並且預設展開第n組,n從0開始計數,則只需要新增如下程式碼:

[java] view plaincopy
  1. myExpandableListView.setBackgroundResource(R.drawable.background);  
  2. myExpandableListView.expandGroup(0);  

則效果如下:


改變每個組前面的圖示,並且圖示樣式隨著合攏和展開不同,則只需要在res/drawable目錄下定義檔案:Indicator.xml

[html] view plaincopy
  1. <selector xmlns:android="http://schemas.android.com/apk/res/android">    
  2.     <item android:state_expanded="true" android:drawable="@drawable/right" />    
  3.     <item android:drawable="@drawable/down"></item>    
  4. </selector>  

在JAVA檔案中新增:

[java] view plaincopy
  1. myExpandableListView.setGroupIndicator(this.getResources().getDrawable(R.drawable.indicator));  
效果如下:


對於其他的屬性設定,可以參考以下屬性說明:

android:childDivider

來分離子列表項的圖片或者是顏色。注:圖片不會完全顯示,分離子列表項的是一條直線

android:childIndicator

在子列表項旁邊顯示的指示符。注:可以是一個圖片

android:childIndicatorLeft

子列表項指示符的左邊約束位置。注:即從左端0位置開始計數,比如,假設指示符是一個圖示,給定這個屬性值為3dip,則表示從左端起3dip開始顯示此圖示。

android:childIndicatorRight

子列表項指示符的右邊約束位置。注:表示右端到什麼位置結束

android:groupIndicator

在組列表項旁邊顯示的指示符。注:可以是一個圖片。

android:indicatorLeft

組列表項指示器的左邊約束位置。注:表示左端從什麼位置開始。

android:indicatorRight

組列表項指示器的右邊約束位置。注:表示右端到什麼位置結束。

 

當然,還可以使用自定義的View去描述group和child,自定義的View可以和佈局檔案一樣,寫在layout資料夾下。例如命名為group.xml或者child.xml。

例如,我們定義一個child項由一個ImageView和一個TextView來組成,則可以定義child.xml為:

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout  
  3. xmlns:android="http://schemas.android.com/apk/res/android"  
  4. android:orientation = "horizontal"  
  5. android:layout_width="match_parent"  
  6. android:layout_height="match_parent">  
  7. <ImageView  
  8. android:layout_gravity = "center_vertical"  
  9. android:id = "@+id/imageView01"  
  10. android:layout_width = "70px"  
  11. android:layout_height = "70px"  
  12. android:paddingLeft = "30px"  
  13. android:paddingTop = "2px"  
  14. android:paddingBottom = "5px"  
  15. android:src = "@drawable/ic_launcher"/>  
  16. <TextView  
  17. android:layout_gravity = "center_vertical"  
  18. android:id = "@+id/childTV"  
  19. android:layout_width = "match_parent"  
  20. android:layout_height = "match_parent"  
  21. android:paddingLeft = "30px"  
  22. android:paddingTop = "10px"  
  23. android:paddingBottom = "5px"  
  24. android:textSize = "30sp"/>  
  25. </LinearLayout>  

對於其中的屬性不再做詳細說明。

對於ExpandableListView中的資料,還可以用以下方式定義:

[java] view plaincopy
  1. List<Map<String, String>> groups = new ArrayList<Map<String, String>>();  
  2.         Map<String, String> group1 = new HashMap<String, String>();  
  3.         group1.put("group""國家");  
  4.          … …   
  5.         groups.add(group1);  
  6. … …  
  7.         //準備第一個一級列表中的二級列表資料:三個二級列表,分別顯示"魏國"、"蜀國"和"吳國"  
  8.         List<Map<String, String>> child1 = new ArrayList<Map<String, String>>();  
  9.         Map<String, String> child1Data1 = new HashMap<String, String>();  
  10.         child1Data1.put("child""魏國");  
  11. … …          
  12. child1.add(child1Data1);  
  13.          … …  
  14.         //準備第二個一級列表中的二級列表資料:八個二級列表,顯示"關羽"、"張飛"、"典韋"、"呂布"、"曹操"、"甘寧"、"郭嘉"、"周瑜"  
  15.         List<Map<String, String>> child2 = new ArrayList<Map<String, String>>();  
  16.         Map<String, String> child2Data1 = new HashMap<String, String>();  
  17.         child2Data1.put("child""關羽");  
  18.         … …  
  19.         child2.add(child2Data1);  
  20.         … …  
  21.   
  22.         //準備第三個一級列表中的二級列表資料:五個二級列表,顯示 "青龍偃月刀"、"丈八蛇矛槍"、 "青鋼劍"、"麒麟弓"、"銀月槍"   
  23.         List<Map<String, String>> child3 = new ArrayList<Map<String, String>>();  
  24.         Map<String, String> child3Data1 = new HashMap<String, String>();  
  25.         child3Data1.put("child""青龍偃月刀");  
  26.             … …  
  27.         child3.add(child3Data1);  
  28.         … …  
  29.           
  30.       //用一個list物件儲存所有的二級列表資料  
  31.         List<List<Map<String, String>>> childs = new ArrayList<List<Map<String, String>>>();  
  32.         childs.add(child1);  
  33.         childs.add(child2);  
  34.         childs.add(child3);  
  35. 針對上述資料定義方式,修改java檔案:  
  36. 例如:在getChildView函式中做如下編寫,  
  37.             String text = groups.get(groupPosition).get("group");  
  38.             LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  39.             //獲取一級列表佈局檔案,設定相應元素屬性  
  40.             LinearLayout linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.group, null);  
  41.             TextView textView = (TextView)linearLayout.findViewById(R.id.textView01);  
  42.             textView.setText(text);  
  43.             return linearLayout;  

這樣就可以將自定義的View寫入到child中,當然,這裡也可以不用佈局檔案來定義View,也可以自己用程式碼實現View。

補充知識:

對於ExpandableListView相應的,也有一個ExpandableListActivity與之對應,對於只需要一個ExpandableListView的Activity,則只需要使用ExpandableListActivity來完成相應的功能就可以了。但是需要注意一點的是:在main.xml頁面中新增如下程式碼:

[html] view plaincopy
  1. <ListView android:id="@android:id/list" 或android:id="@id/android:list"  
  2. android:layout_width="fill_parent"  
  3. android:layout_height="wrap_content">  
  4. </ListView>  

這個ID不能隨便修改,否則會出現異常:java.lang.RuntimeException:Your content must have a ExpandableListView whose id attribute is'android.R.id.list'.

相關文章