Android Kotlin Expandable Listview

Android Expandable Listview Tutorial Kotlin

Introduction:

ExpandableListView in Android is a View which is vertically scroll-able like normal ListView but it can have child items for each List Item.

Child items in Expandable List View can be collapsed or expanded on clicking.

Add ExpandableListView in XML Layout file


<android.support.constraint.ConstraintLayout 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="match_parent">

<ExpandableListView
android:id="@+id/rvFaqs"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:groupIndicator="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Add XML Layout file for ExpandableListView parent Item

Our Parent Item for ExpandableListView will have One TextView and One ImageView.

When ever user will click on any parent item in ListView it will be expanded or collapse, if already expanded 🙂 .

// item_faqs_parent.xml 

<?
xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="wrap_content"
android:padding="16dp">

<TextView
android:id="@+id/txtFaqs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/imgIonNextFaq"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_next_blue" />
</android.support.constraint.ConstraintLayout>

Add XML Layout file for ExpandableListView Child Item

<?xml version="1.0" encoding="utf-8"?>
<com.es.mewphone.widgets.LightText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/txtFaqAnswer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="16dp"
android:textColor="@color/colorTextdefault" />

Create Model Class for Data

We will send List of type FAQ (Kotlin Data Class) to our ExpandableListView Adapter class and it will show these Faq’s and their answers in List.


data class Faq(
val categoryId: String,
val categoryName: String,
val faqId: String,
val question: String,
val answer: String
)

Create Adapter Class for ExpandableListView

Our Adapter class will extend BaseExpandableListAdapter class.

Following will be override functions which we will implement from BaseExpandableListAdapter class.

getChild – This function will return child item (String) to be displayed as child.

getChildView – This function will inflate child xml layout file and will return child view.

getGroup – This function will return parent item (String) to be displayed as parent.

getGroupView – This function will inflate parent xml layout file and will return parent view.

getGroupCount – This function will return total number of parent items.

getChildrenCount – This will return total number of child items for each parent item. (Will always return 1 in our case).

import java.util.HashMap

import android.content.Context
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import android.widget.TextView
import com.es.mewphone.R
import com.es.mewphone.data.faqs.Faq
import kotlinx.android.synthetic.main.item_billpayment_parent_list.view.*
import kotlinx.android.synthetic.main.item_faqs_child.view.*
import kotlinx.android.synthetic.main.item_faqs_parent.view.*

class ExpandableFaqsListAdapter(val _context: Context, var _listDataHeader: ArrayList<Faq>, private val _listChild: List<Faq> // header titles
// child data in format of header title, child title
) : BaseExpandableListAdapter() {


var _listDataHeaderFiltered: ArrayList<Faq>
var _listDataHeaderOriginal = ArrayList<Faq>()

init {
_listDataHeaderFiltered = _listDataHeader
_listDataHeaderOriginal.addAll(_listDataHeader)
}

override fun getChild(groupPosition: Int, childPosititon: Int): Any {
return _listDataHeaderFiltered[groupPosition].answer
}

override fun getChildId(groupPosition: Int, childPosition: Int): Long {
return childPosition.toLong()
}

override fun getChildView(groupPosition: Int, childPosition: Int,
isLastChild: Boolean, convertView: View?, parent: ViewGroup): View {
var convertView = convertView

val childText = getChild(groupPosition, childPosition) as String

if (convertView == null) {
val infalInflater = this._context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
convertView = infalInflater.inflate(R.layout.item_faqs_child, null)
}

convertView!!.txtFaqAnswer.text = childText

/*val txtListChild = convertView!!
.findViewById(R.id.lblListItem) as TextView
txtListChild.text = childText
*/
return convertView!!
}

override fun getChildrenCount(groupPosition: Int): Int {
return 1
}

override fun getGroup(groupPosition: Int): Any {
return this._listDataHeaderFiltered[groupPosition].question
}

override fun getGroupCount(): Int {
return this._listDataHeaderFiltered.size
}

override fun getGroupId(groupPosition: Int): Long {
return groupPosition.toLong()
}

override fun getGroupView(groupPosition: Int, isExpanded: Boolean,
convertView: View?, parent: ViewGroup): View {
var convertView = convertView
val headerTitle = getGroup(groupPosition) as String
if (convertView == null) {
val infalInflater = this._context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
convertView = infalInflater.inflate(R.layout.item_faqs_parent, null)
}

if (groupPosition % 2 == 1) {
convertView?.setBackgroundResource(R.color.colorLightGrayBackground)
} else {
convertView?.setBackgroundResource(R.color.colorDarkGrayBackground)
}

convertView!!.txtFaqs.text = headerTitle

return convertView!!
}

override fun hasStableIds(): Boolean {
return false
}

override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
return true
}
}

As now we are done with Setting up Adapter class for our ExpandableListView, now we will set adapter for ExpandableListView and will pass it required data (list of Faq’s).

class FaqsFragment :Fragment() {

lateinit var faqsListView: ExpandableListView
lateinit var listAdapter: ExpandableFaqsListAdapter
lateinit var faqsView: View

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)

faqsView = inflater?.inflate(R.layout.fragment_faqs, container, false)

faqsListView = faqsView?.findViewById(R.id.rvFaqs) as ExpandableListView

listAdapter = ExpandableFaqsListAdapter(activity!!, faqsListParent,faqsListChild )
faqsListView.setAdapter(listAdapter)

// If you want to change parent item icon (up/down/expanded/collapse)
// effect. Add onGroupClick Listener like below.

faqsListView.setOnGroupClickListener { expandableListView, view, i, l ->
if (expandableListView.isGroupExpanded(i)) {
var imgView = view.findViewById<ImageView>(R.id.imgIonNextFaq)
imgView.setImageDrawable(ContextCompat.getDrawable(this@FaqsFragment.context!!, R.drawable.ic_next_blue))
} else {
var imgView = view.findViewById<ImageView>(R.id.imgIonNextFaq)
imgView.setImageDrawable(ContextCompat.getDrawable(this@FaqsFragment.context!!, R.drawable.ic_blue_down))
}
return@setOnGroupClickListener false
}


return faqsView
}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}

Bonus Code 🙂

If you need to filter ExpandableListView based on EditText Search or Spinner.

Add this code in your Adapter class.

fun filterFaqList(query: String) {

_listDataHeaderFiltered.clear()
if (query.isEmpty()) {
_listDataHeaderFiltered.addAll(_listDataHeaderOriginal)
} else {
for (faqs in _listDataHeaderOriginal) {
if (faqs.question.contains(query, ignoreCase = true)) {
_listDataHeaderFiltered.add(faqs)
}
}
}
notifyDataSetChanged()
}

Add this code in your Fragment class.

faqsView.etSearchFaqs.addTextChangedListener(object : TextWatcher {

override fun afterTextChanged(s: Editable?) {

listAdapter.filterFaqList(faqsView.etSearchFaqs.text.toString())

}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})

Conclusion:

We have learned how to:

  • Implement ExpandableListView in Android using Kotlin.
  • How to filter ListView based on search query using Kotlin Code.
  • How to show collapse/expand effect with the help of ImageView.

copyright :  http://developine.com/

Hello! I am Hammad Tariq. Jack of All trades and Yet master of Many! Senior Android Application Developer | Cross-platform Development | Flutter | Backend Development (Spring Boot + Kotlin + MySQL) | Blogger | Founder I am an all-around Mobile Developer who is passionate about learning the latest technologies and trends. I graduated from Air University Islamabad, Pakistan in 2015 but started working on Android Development, Universal Windows Apps development long before that. Currently, I am working as a team lead and Senior Android Developer at EVAMP SAANGA, Pakistan.
Posts created 40

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top