恥ずかしながら案件を受けるまでやったことがなく、意外と検索してもこれというサンプルが見つからなかったので、備忘録として残します。
※すみません、ベースはどなたかの車のサンプルコード参考に改修しました。
ポイントは
- Kotlin
- ExpandableListViewのカスタマイズ
- 矢印などのインジケーターをカスタムかつ右に配置
- インジケータのサイズ調整
- チェックボックスやラジオボタンをカスタム表示
百聞は一見にしかずということでソースコードはこちら。
テスト作成なので、冗長なコードは修正くださいm ( _ _ ) m
ソースコード
リストを表示するFragment
ExpandableListViewの用意したデータをもとにアダプター作成してセットするだけ
package com.pgworkslabo.testexpandablelistview
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ExpandableListView
import android.widget.Toast
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_first.*
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//ExpandableListViewの初期化
val exListView: ExpandableListView = exlistview
activity?.applicationContext?.also {
val adapter = CarMakerListAdapter(it, ConditionData.data.first, ConditionData.data.second)
exListView.setAdapter(adapter)
exListView.setOnChildClickListener { parent, _, groupPosition, childPosition, _ -> //子要素をタップした時の処理
//このサンプルではToastメッセージを表示するだけ
(parent.expandableListAdapter as CarMakerListAdapter).also { adapt ->
val mName = adapt.getGroup(groupPosition) as String //親要素からメーカー名を取得
val cName = adapt.getChild(groupPosition, childPosition) as String //子要素から車名を取得
Toast.makeText(
it,
"$mName : $cName",
Toast.LENGTH_LONG
).show() //Toast生成
}
true
}
// この方法でもインジケーターが反転する
// val vto: ViewTreeObserver = exListView.viewTreeObserver
//
// vto.addOnGlobalLayoutListener {
// exListView.setIndicatorBounds(exListView.right -160, exListView.width)
// }
}
}
}
BaseExpandableListAdapterを継承したアダプタ
iOSのSectionにあたる親とRowにあたる子の描画をカスタマイズ
package com.pgworkslabo.testexpandablelistview
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import kotlinx.android.synthetic.main.listitem_cars.view.*
import kotlinx.android.synthetic.main.listitem_makers.view.*
internal class CarMakerListAdapter //コンストラクタ
(
var context: Context,
//メンバ変数
var listMaker //親要素のリスト
: List<String>,
//子要素のリスト
var listCar: List<List<String>>
) :
BaseExpandableListAdapter() {
override fun getGroupCount(): Int {
return listMaker.size //親要素の数を返す
}
override fun getChildrenCount(groupPosition: Int): Int {
return listCar[groupPosition].size //子要素の数を返す
}
override fun getGroup(groupPosition: Int): Any {
return listMaker[groupPosition] //親要素を取得
}
override fun getChild(groupPosition: Int, childPosition: Int): Any {
return listCar[groupPosition][childPosition] //子要素を取得
}
override fun getGroupId(groupPosition: Int): Long {
//親要素の固有IDを返す
//このサンプルでは固有IDは無いので0
return 0
}
override fun getChildId(groupPosition: Int, childPosition: Int): Long {
//子要素の固有IDを返す
//このサンプルでは固有IDは無いので0
return 0
}
override fun hasStableIds(): Boolean {
//固有IDを持つかどうかを返す
//このサンプルでは持たないのでfalse
return false
}
override fun getGroupView(
groupPosition: Int,
isExpanded: Boolean,
convertView: View?,
parent: ViewGroup
): View? {
var cv = if (convertView == null) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.listitem_makers, parent, false)
} else {
convertView
}
cv?.item_makers_name.also {
it?.text = listMaker[groupPosition]
}
return cv
}
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup
): View? {
//子要素のレイアウト生成
var cv = convertView
if (cv == null) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
cv = inflater.inflate(R.layout.listitem_cars, parent, false)
}
cv?.item_cars_name.also {
it?.text = listCar[groupPosition][childPosition]
}
when(listMaker[groupPosition]){
"TOYOTA"->{
cv?.condition_check_box?.visibility = View.VISIBLE
cv?.condition_radio_button?.visibility = View.GONE
}
else->{
cv?.condition_check_box?.visibility = View.GONE
cv?.condition_radio_button?.visibility = View.VISIBLE
}
}
return cv
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
//子要素がタップ可能かどうかを返す
//このサンプルでは可能にするのでtrue
return true
}
}
データ作成
セクションとローのデータ
package com.pgworkslabo.testexpandablelistview
import java.util.*
internal object ConditionData {
val data: Pair<List<String>, List<List<String>>>
get() {
//親要素のリスト
val makers: MutableList<String> = ArrayList()
makers.add("TOYOTA")
makers.add("MAZDA")
makers.add("HONDA")
//子要素のリスト
val carsToyota: MutableList<String> = ArrayList()
carsToyota.add("CROWN")
carsToyota.add("PRIUS")
carsToyota.add("COROLLA")
carsToyota.add("VITZ")
val carsMazda: MutableList<String> = ArrayList()
carsMazda.add("ATENZA")
carsMazda.add("AXELA")
carsMazda.add("DEMIO")
val carsHonda: MutableList<String> = ArrayList()
carsHonda.add("LEGEND")
carsHonda.add("CIVIC")
carsHonda.add("FIT")
val cars: MutableList<List<String>> =
ArrayList()
cars.add(carsToyota)
cars.add(carsMazda)
cars.add(carsHonda)
return Pair(makers, cars)
}
}
レイアウト
AndroidJavaやってた人にはお決まりですかね
Fragment表示
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<ExpandableListView
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indicatorLeft="10dp"
android:indicatorRight="60dp"
android:groupIndicator="@drawable/condition_indicator"
android:layoutDirection="rtl"
android:id="@+id/exlistview"/>
</androidx.constraintlayout.widget.ConstraintLayout>
セクション部分のメーカーのレイアウト
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/item_makers_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test"
android:padding="16sp"
android:layoutDirection="ltr"
android:textColor="@color/cardview_dark_background"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
メーカーの子にあたる車のレイアウト
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/item_cars_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test"
android:padding="16sp"
android:layout_marginLeft="12dp"
android:layoutDirection="ltr"
android:textColor="@color/cardview_dark_background"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<CheckBox
android:visibility="visible"
android:id="@+id/condition_check_box"
style="@style/ConditionCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_gravity="right|center_vertical"
/>
<RadioButton
android:visibility="visible"
android:id="@+id/condition_radio_button"
style="@style/ConditionRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_gravity="right|center_vertical"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
表示画像をカスタムするためのcheckboxのdrawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:drawable="@drawable/checkboxon" />
<item
android:state_checked="false"
android:drawable="@drawable/checkboxoff" />
</selector>
表示画像をカスタムするためのradio buttonのdrawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:drawable="@drawable/downloadon" />
<item
android:state_checked="false"
android:drawable="@drawable/downloadoff" />
</selector>
indicatorを右に配置、アイコンサイズを調整するdrawable
結局left,right,top,bottomをpaddingのように利用。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_empty="true">
<layer-list>
<item
android:left="10dp"
android:right="10dp"
android:top="10dp"
android:bottom="10dp"
android:drawable="@drawable/list_close">
</item>
</layer-list>
</item>
<item android:state_expanded="true">
<layer-list>
<item
android:left="10dp"
android:right="10dp"
android:top="10dp"
android:bottom="10dp"
android:drawable="@drawable/list_open">
</item>
</layer-list>
</item>
<item>
<layer-list>
<item
android:left="2dp"
android:right="2dp"
android:top="10dp"
android:bottom="10dp"
android:drawable="@drawable/list_close">
</item>
</layer-list>
</item>
</selector>
チェックボックスとラジオボタンのstyle
<resources>
<style name="ConditionCheckBox" parent="android:Widget.CompoundButton.CheckBox">
<item name="android:button">@drawable/condition_checkbox</item>
</style>
<style name="ConditionRadioButton" parent="android:Widget.CompoundButton.RadioButton">
<item name="android:button">@drawable/condition_radiobutton</item>
</style>
</resources>