恥ずかしながら案件を受けるまでやったことがなく、意外と検索してもこれというサンプルが見つからなかったので、備忘録として残します。
※すみません、ベースはどなたかの車のサンプルコード参考に改修しました。
ポイントは
- Kotlin
- ExpandableListViewのカスタマイズ
- 矢印などのインジケーターをカスタムかつ右に配置
- インジケータのサイズ調整
- チェックボックスやラジオボタンをカスタム表示
百聞は一見にしかずということでソースコードはこちら。
テスト作成なので、冗長なコードは修正くださいm ( _ _ ) m
ソースコード
リストを表示するFragment
ExpandableListViewの用意したデータをもとにアダプター作成してセットするだけ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
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にあたる子の描画をカスタマイズ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
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 } } |
データ作成
セクションとローのデータ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?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> |
セクション部分のメーカーのレイアウト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?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> |
メーカーの子にあたる車のレイアウト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?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
1 2 3 4 5 6 7 8 9 |
<?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
1 2 3 4 5 6 7 8 9 |
<?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のように利用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<?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
1 2 3 4 5 6 7 8 9 |
<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> |