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.
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:
Publicación traducida automáticamente
Artículo escrito por satheeshguduri y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA