RecyclerView expandible en Android con Kotlin

En este artículo, conoceremos cómo implementar el RecycleView expandible. En general, mostraremos la lista de elementos en la vista de lista, pero en algunas situaciones, como si desea mostrar los elementos de la sublista, entonces tenemos que usar una lista expandible. Vea la imagen de abajo para una mejor comprensión.

Expandable RecyclerView in Android with

vista de reciclaje expandible

Implementación paso a paso

1. Si observa la imagen de arriba, tenemos dos tipos de elementos de lista: uno es padre e hijo. Pero puede pasar una lista de objetos similares a la vista del reciclador. Entonces, tenemos que crear un objeto/modelo común para ambos (padre e hijo)

2. Creación de una clase de modelo común

Kotlin

data class ParentData(
    val parentTitle:String?=null,
    var type:Int = Constants.PARENT,
    var subList : MutableList<ChildData> = ArrayList(),
    var isExpanded:Boolean = false
)

Este es el modelo que usamos para padres e hijos. En el tipo de clase de datos, es por defecto padre, isExpanded, es por defecto falso y sublista vacía. Aquí estamos usando constantes del archivo como se muestra a continuación.

Kotlin

object Constants {
    const val PARENT = 0
    const val CHILD = 1
}

3. Creando clase de datos Child

En este paso, tenemos que crear una clase de datos más para los elementos secundarios como se muestra a continuación.

Kotlin

data class ChildData(val childTitle:String)

4. Configuración de algunos datos para mostrar en la lista

Ahora, agregaremos algunos datos a la lista, que se pasan al adaptador como se muestra a continuación.

Kotlin

val listData : MutableList<ParentData> = ArrayList()

Aquí estamos tomando listData, que se usa para almacenar los objetos ParentData. Ahora creamos datos de muestra de objetos padre e hijo como se muestra a continuación.

Kotlin

val parentData: Array<String> = arrayOf("Andhra Pradesh", "Telangana", "Karnataka", "TamilNadu")
val childDataData1: MutableList<ChildData> = mutableListOf(ChildData("Anathapur"),ChildData("Chittoor"),ChildData("Nellore"),ChildData("Guntur"))
val childDataData2: MutableList<ChildData> = mutableListOf(ChildData("Rajanna Sircilla"), ChildData("Karimnagar"), ChildData("Siddipet"))
val childDataData3: MutableList<ChildData> = mutableListOf(ChildData("Chennai"), ChildData("Erode"))

Ahora es el momento de convertir estos datos en un objeto ParentData porque lo agregaremos a ListData.

Kotlin

val parentObj1 = ParentData(parentTitle = parentData[0], subList = childDataData1)
val parentObj2 = ParentData(parentTitle = parentData[1], subList = childDataData2)
val parentObj3 = ParentData(parentTitle = parentData[2])
val parentObj4 = ParentData(parentTitle = parentData[1], subList = childDataData3)

En el modelo ParentData por defecto, el tipo es el padre y expandible es falso. Ahora estamos agregando título y sublista. Finalmente, necesitamos agregar todos estos objetos a listData como a continuación

Kotlin

listData.add(parentObj1)
listData.add(parentObj2)
listData.add(parentObj3)
listData.add(parentObj4)

5. Creación del adaptador para la vista de reciclaje 

Acabamos de agregar algunos datos ficticios, ahora tenemos que mostrar estos datos en la vista del reciclador, para eso necesitamos un adaptador, que se usa para mostrar nuestros datos según nuestros requisitos.

Kotlin

class RecycleAdapter(var mContext: Context, val list: MutableList<ParentData>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
  
        return if(viewType== Constants.PARENT){
            val rowView: View = LayoutInflater.from(parent.context).inflate(R.layout.parent_row, parent,false)
           GroupViewHolder(rowView)
        } else {
            val rowView: View = LayoutInflater.from(parent.context).inflate(R.layout.child_row, parent,false)
           ChildViewHolder(rowView)
        }
    }
  
    override fun getItemCount(): Int = list.size
  
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
  
        val dataList = list[position]
        if (dataList.type == Constants.PARENT) {
            holder as GroupViewHolder
            holder.apply {
                parentTV?.text = dataList.parentTitle
                downIV?.setOnClickListener {
                    expandOrCollapseParentItem(dataList,position)
                }
            }
        } else {
            holder as ChildViewHolder
  
            holder.apply {
                val singleService = dataList.subList.first()
                childTV?.text =singleService.childTitle
            }
        }
    }
    private fun expandOrCollapseParentItem(singleBoarding: ParentData,position: Int) {
  
        if (singleBoarding.isExpanded) {
            collapseParentRow(position)
        } else {
            expandParentRow(position)
        }
    }
  
    private fun expandParentRow(position: Int){
        val currentBoardingRow = list[position]
        val services = currentBoardingRow.subList
        currentBoardingRow.isExpanded = true
        var nextPosition = position
        if(currentBoardingRow.type==Constants.PARENT){
  
            services.forEach { service ->
                val parentModel =  ParentData()
                parentModel.type = Constants.CHILD
                val subList : ArrayList<ChildData> = ArrayList()
                subList.add(service)
                parentModel.subList=subList
                list.add(++nextPosition,parentModel)
            }
            notifyDataSetChanged()
        }
    }
  
    private fun collapseParentRow(position: Int){
        val currentBoardingRow = list[position]
        val services = currentBoardingRow.subList
        list[position].isExpanded = false
        if(list[position].type==Constants.PARENT){
            services.forEach { _ ->
                list.removeAt(position + 1)
            }
            notifyDataSetChanged()
        }
    }
  
    override fun getItemViewType(position: Int): Int = list[position].type
  
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }
  
    class GroupViewHolder(row: View) : RecyclerView.ViewHolder(row) {
        val parentTV = row.findViewById(R.id.parent_Title) as TextView?
        val downIV  = row.findViewById(R.id.down_iv) as ImageView?
    }
    class ChildViewHolder(row: View) : RecyclerView.ViewHolder(row) {
        val childTV = row.findViewById(R.id.child_Title) as TextView?
  
    }
}

6. Agregar lógica en la clase de adaptador

En este paso, discutiremos cómo el adaptador muestra nuestros datos.  

Kotlin

override fun getItemViewType(position: Int): Int = list[position].type

Esta línea se usa para establecer el tipo de vista; de forma predeterminada, el tipo de objeto ParentData es el principal, por lo que muestra la lista de elementos principales. Pero, ¿por qué estamos agregando este tipo aquí? Cuando vincula los datos a una vista, para saber qué tipo de tipo de vista proviene del método del adaptador como se muestra a continuación

Kotlin

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return   if(viewType== Constants.PARENT){
            val rowView: View = LayoutInflater.from(parent.context).inflate(R.layout.parent_row, parent,false)
           GroupViewHolder(rowView)
        } else{
            val rowView: View = LayoutInflater.from(parent.context).inflate(R.layout.child_row, parent,false)
           ChildViewHolder(rowView)
        }
    }

Este método viene con viewType, que ya está configurado antes. Ahora tenemos la oportunidad de comprobar qué tipo viene. Si es un padre, podemos inflar (convertir el diseño en vista) parent_row o child_row y devolver GroupViewHolder (rowView) o ChildViewHolder (rowView)

padre_fila.xml

XML

<?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_marginBottom="4dp"
    android:background="@color/lightblue"
    android:layout_height="50dp">
  
    <TextView
        android:id="@+id/parent_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.099"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.419" />
  
    <ImageView
        android:id="@+id/down_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_dropdown"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.896"
        app:layout_constraintStart_toEndOf="@+id/parent_Title"
        app:layout_constraintTop_toTopOf="parent" />
  
</androidx.constraintlayout.widget.ConstraintLayout>

niño_fila.xml

XML

<?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"
    android:layout_width="match_parent"
    android:layout_marginBottom="4dp"
    android:background="@color/purple_200"
    android:layout_height="30dp">
  
    <TextView
        android:id="@+id/child_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.099"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.036" />
  
  
</androidx.constraintlayout.widget.ConstraintLayout>

7. Cómo vincular los datos a las vistas

Kotlin

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
 
       val dataList = list[position]
       if (dataList.type == Constants.PARENT) {
           holder as GroupViewHolder
           holder.apply {
               parentTV?.text = dataList.parentTitle
               downIV?.setOnClickListener {
                   expandOrCollapseParentItem(dataList,position)
               }
           }
       } else {
           holder as ChildViewHolder
 
           holder.apply {
               val singleService = dataList.subList.first()
               childTV?.text =singleService.childTitle
           }
       }
   }

Este método viene con un soporte. Ahora tenemos que verificar usando el tipo si es un padre, entonces podemos usar la palabra clave as (instancia de en Java) para vincular la vista de datos como titular como GroupViewHolder.

8. Cómo funciona la lógica de expandir/contraer

Cuando el usuario hace clic en la imagen de la flecha hacia abajo, llamamos a la función expandOrCollapseParentItem. Esta función verifica si el padre está expandido o no como se muestra a continuación.

Kotlin

private fun expandOrCollapseParentItem(singleBoarding: ParentData,position: Int) {
        if (singleBoarding.isExpanded) {
            collapseParentRow(position)
        } else {
            expandParentRow(position)
        }
    }

Primero, verá la funcionalidad expandParentRow 

Kotlin

private fun expandParentRow(position: Int){
       val currentBoardingRow = list[position]
       val services = currentBoardingRow.subList
       currentBoardingRow.isExpanded = true
       var nextPosition = position
       if(currentBoardingRow.type==Constants.PARENT){
 
           services.forEach { service ->
               val parentModel =  ParentData()
               parentModel.type = Constants.CHILD
               val subList : ArrayList<ChildData> = ArrayList()
               subList.add(service)
               parentModel.subList=subList
               list.add(++nextPosition,parentModel)
           }
           notifyDataSetChanged()
       }
   }

Veamos cómo funciona la lógica de expansión cuando se llama a la función expandParentRow con la posición. Obtenemos los datos por posición de donde obtenemos la sublista. Nuevamente, estamos agregando elementos de sublista usando para cada uno y aumentando la posición actual con +1 como se muestra a continuación

Kotlin

list.add(++nextPosition,parentModel)

aquí, estamos agregando parentModel a la lista y agregando el tipo como Constants.CHILD, después de agregar los elementos que llamamos notificarDataSetChanged(), por lo que nuevamente la vista del reciclador se cargará con nuevos datos. Mientras vincula los datos nuevamente, verificará el tipo y los datos en su vista. La misma lógica funciona para el colapso y allí haremos la operación inversa. Finalmente, necesitamos agregar este adaptador a RecyclerView como se muestra a continuación.

Kotlin

val exRecycleView = findViewById<RecyclerView>(R.id.exRecycle)
        exRecycleView.layoutManager = LinearLayoutManager(this)
        exRecycleView.adapter = RecycleAdapter(this@MainActivity,listData)

y nuestro archivo main_layout.xml donde agregamos la vista del reciclador.

XML

<?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=".MainActivity">
  
  
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/exRecycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
  
</androidx.constraintlayout.widget.ConstraintLayout>

Producción:

Output

Publicación traducida automáticamente

Artículo escrito por satheeshguduri y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *