Android-雙層清單ExpandableListView
對初學者來說,應該都曾經學過ListView,它是一個能用來顯示清單的元件。不過ListView是「單層式」的,只能簡單地顯示一條一條的項目。
本文要介紹的ExpandableListView是「雙層式」,外觀像ListView,但是每個群組項目可以繼續點擊展開或收合,裡頭包含了多個子項目。實作方式也會稍微繁瑣一些。本文使用Kotlin語言。
一、添加Layout元件
首先在Activity的XML檔加入ExpandableListView這個元件
<ExpandableListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#800000FF"
android:childDivider="#80FF0000"
android:dividerHeight="1dp"/>
下面介紹一些實用的屬性
divider、childDevider:分別為群組項目與子項目的分隔線顏色。
groupIndicator:群組項目前面的icon,預設為根據展開或收合來顯示上下箭頭。若不想要這個icon,可設值為「@null」。
childIndicator:子項目前面的icon,預設無圖案。
dividerHeight:分隔線的粗細,不分群組項目或子項目,是統一設定的。
除此之外,群組項目和子項目也需要對應的Layout,這裡先定義如下,下面會再解說本文範例的情境。
群組項目的版面配置(item_department.xml)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtDepartmentName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginStart="40dp"
android:textSize="20sp"
android:textColor="#0000FF"/>
</LinearLayout>
子項目的版面配置(item_class.xml)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtClassName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginStart="40dp"
android:textSize="17sp"
android:textColor="#FF0000"/>
</LinearLayout>
二、建立Adapter
ListView需要Adapter來串接資料和項目畫面,ExpandableListView也是如此。但與ListView不同的是,它使用「BaseExpandableListAdapter」,這樣才能分別串接群組項目及子項目。
class ExpandableListViewAdapter()
: BaseExpandableListAdapter() {
}
繼承BaseExpandableListAdapter後,需要實作10個方法。我們在下一節再實作它們,這一節先說明要顯示的測試資料,並完成Adapter的建構式。
實作ListView的BaseAdapter時,一個簡單的例子就是透過建構式傳入一個List。但ExpandableListView是雙層的,因此筆者在本文的範例中需要同時傳入群組項目和子項目的List。
本文的範例是:群組項目為「科系名稱」,子項目為「班級名稱」,因此在建構式加入兩個List的參數。
class ExpandableListViewAdapter(
private val context: Context,
private val departments: List<String>,
private val classes: List<List<String>>)
: BaseExpandableListAdapter() {}
其中班級名稱使用「List<List<String>>」的理由是,每個科系中會有多個班級。為了儲存所有科系的班級名稱,需要在一個List中存放各個群組的班級List。而傳入Context是為了之後載入Layout檔使用。
三、實作Adapter
在上一節繼承BaseExpandableListAdapter後,會發現有10個方法要實作,本節就來一一完成,其實大部份都跟ListView的BaseAdapter有對應關係。
1.取得群組物件和子項目物件
override fun getGroup(groupPosition: Int): Any {
return departments[groupPosition]
}override fun getChild(groupPosition: Int, childPosition: Int): Any {
return classes[groupPosition][childPosition]
}
2.取得群組數量和其內部子項目的數量
override fun getGroupCount(): Int {
return departments.size
}override fun getChildrenCount(groupPosition: Int): Int {
return classes[groupPosition].size
}
3.項目ID的相關設定,依照需求決定回傳值即可,適當運用將對效能有幫助
override fun getGroupId(groupPosition: Int): Long {
return groupPosition.toLong()
}override fun getChildId(groupPosition: Int, childPosition: Int): Long {
return (groupPosition * 100 + childPosition).toLong()
}override fun hasStableIds(): Boolean {
return true
}
4.定義子項目是否可以被點擊。設為true才能進而觸發點擊事件,設為false的話會讓分隔線一併消失。
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
return true
}
5.串接資料與項目畫面
override fun getGroupView(groupPosition: Int, isExpanded: Boolean, convertView: View?, parent: ViewGroup?): View {
val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.item_department, null)
val textView = view.findViewById<TextView>(R.id.txtDepartmentName)
textView.text = departments[groupPosition]
return view
}override fun getChildView(groupPosition: Int, childPosition: Int, isLastChild: Boolean, convertView: View?, parent: ViewGroup?): View {
val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.item_class, null)
val textView = view.findViewById<TextView>(R.id.txtClassName)
textView.text = classes[groupPosition][childPosition]
return view
}
四、觀看結果
在Activity準備好測試資料,建立Adapter,並指派給ExpandableListView就完成了。
val listView = findViewById<ExpandableListView>(R.id.listView)
val departments = listOf("資訊管理系", "財務金融系", "會計系")
val classes = listOf(
listOf("一年甲班", "二年甲班", "三年甲班", "四年甲班"),
listOf("一年甲班", "二年甲班", "三年甲班", "四年甲班"),
listOf("一年甲班", "一年乙班", "二年甲班", "二年乙班",
"三年甲班", "三年乙班", "四年甲班", "四年乙班")
)
val adapter = ExpandableListViewAdapter(this, departments, classes)listView.setAdapter(adapter)
五、點擊事件
點擊群組項目。若回傳值為true,則不會展開清單。
listView.setOnGroupClickListener { parent, v, groupPosition, id ->
val departmentName = adapter.getGroup(groupPosition).toString()
Toast.makeText(this@MainActivity, departmentName, Toast.LENGTH_SHORT).show()
false
}
點擊子項目
listView.setOnChildClickListener { parent, v, groupPosition, childPosition, id ->
val departmentName = adapter.getGroup(groupPosition).toString()
val className = adapter.getChild(groupPosition, childPosition).toString()
val classTitle = "$departmentName$className"
Toast.makeText(this@MainActivity, classTitle, Toast.LENGTH_SHORT).show()
false
}