Generalidades

RecyclerView com múltiplos layout – Kotlin | Autor: Otavio Moreira | Agosto 2021

Eu tive a needidade de usar múltiplos layouts em um mesmo RecyclerView quando precisióni renderizar um formulário, mas na rolagem da tela o cabeçalho deveria estar semper no topo das qstões eo botixão de enviraumsar. RecyclerView para funso, pensei enviraumsar. Realmente funcionou.

Neste artigo, estou explicando tudo nos minimos delectrónicotalhes para tentar sanar todas como dúvidas, então poravour, não se irrite caso o artigo seja grande demais para você, ele vai te ajudar caso esta seja sua necessidade. E também, no se assuste pelo tamanho, é bem simples de deploymentar o RecyclerView com vários layout.

Caso queira apenas ver o código-fonte, aquí está o link do repositório no GitHub.

No final do exeplo que vou mostrar, o resultado será este:

Resultado final do exemplo utilizando RecyclerView com múltiplos layouts

Então, recomendo que crie um projeto do zero no Android Studio com template «Empty Activity» e nomeia com algo como «Múltiplos layouts» ou algo da sua preferenciaência.

1º Paso

Adicionar o RecyclerView sin diseño activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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"
android:orientation="vertical"
tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.
widget.LinearLayoutManager>

</androidx.appcompat.widget.LinearLayoutCompat>

2º Paso

Declaración o RecyclerView para uma variável no MainActivity.kt.

class MainActivity : AppCompatActivity() 

override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val recyclerView = findViewById<RecyclerView>(R.id.recycler)


3º Paso

Criar a classe do Adapter e nomear como DataAdapter, e ter como parâmetro uma variável do tipo Context. Clase Nessa, ampliada RecyclerView.Adapter<>()`, mas ainda não informe nenhum tipo de ViewHolder.

class DataAdapter(
private val context: Context
) : RecyclerView.Adapter<>()

4º Paso

A função que vamos a utilizar para diferenciar qual layout o RecyclerView deve usar vai utilizar um parâmetro do tipo inteiro, então devemos definir utilizando inteiro quais vão ser nossos tipos. Neste ejemplo, existen sólo 3 tipos de datos, o Encabezado (cabeçalho), Pregunta (pergunta) e Botón (botão). Vamos declarar estas constantes dentro do companion object Categoría grande DataAdapter.

class DataAdapter(
private val context: Context
) : RecyclerView.Adapter<>()

companion object
private const val TYPE_HEADER = 0
private const val TYPE_QUESTION = 1
private const val TYPE_BUTTON = 2

5º Paso

Sin adaptador de um RecyclerView, criamos uma classe que extende RecyclerView.ViewHolder<T>() para manipular o elemento que vai ser criado e vamos a criar diversos tipos de elemento com seus peerivos layouts, precisamos criar uma classe base e extender dos elementos específicos, passando seusrespectivos tipo de dados.

Crie uma classe abstrata e nomeie de “BaseViewHolder” com o método abstrato “bind”. Esta classe deve receber o tipo “T” para que posamos passar qualquer tipo de dado para una clase quando extensor dela, eo parâmetro do método deve conter o mesmo tipo “T”.

class DataAdapter(
private val context: Context
) : RecyclerView.Adapter<>()

companion object
private const val TYPE_HEADER = 0
private const val TYPE_QUESTION = 1
private const val TYPE_BUTTON = 2

abstract class BaseViewHolder<T>(
itemView: View
) : RecyclerView.ViewHolder(itemView)
abstract fun bind(item: T)

6º Paso

Agora podemos informar para a extensão da nossa classe DataAdapter`o tipo de ViewHolder que vamos criar, mas vamos informar a nossa classe base informando o tipo dela como um asterísco

class DataAdapter(
private val context: Context
) : RecyclerView.Adapter<DataAdapter.BaseViewHolder<*>>()

companion object
private const val TYPE_HEADER = 0
private const val TYPE_QUESTION = 1
private const val TYPE_BUTTON = 2

abstract class BaseViewHolder<T>(
itemView: View
) : RecyclerView.ViewHolder(itemView)
abstract fun bind(item: T)

indicando que aceitar qualquer tipo de dado.

7º Paso inner class Llorar en BaseViewHolderpara os três ViewHolders que precisamos , extendendo una base de clases

inner class HeaderViewHolder(
itemView: View
) : BaseViewHolder<Header>(itemView)
override fun bind(item: Header)

inner class QuestionViewHolder(
itemView: View
) : BaseViewHolder<Question>(itemView)
override fun bind(item: Question)

inner class ButtonViewHolder(
itemView: View
) : BaseViewHolder<Button>(itemView)
override fun bind(item: Button)

.

8º Paso

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BaseViewHolder<*>
TODO("Not yet implemented")

override fun onBindViewHolder(
holder: BaseViewHolder<*>,
position: Int
)
TODO("Not yet implemented")

override fun getItemCount(): Int
TODO("Not yet implemented")

Agora, implemente amos os métodos obrigatórios do RecyclerView utilizando un IDE própria. Estes são os métodos que serão deploymentados:

9º Paso getItemViewType()Implementador o método do Recycler

override fun getItemViewType(position: Int): Int 
return super.getItemViewType(position)

.

10º Paso companion objectAgora vamos a criar a nossa lista que vai conter os dados para mostrar no RecyclerView. Ela deve ser do tipo «Any» para poder suportar qualquer tipo de dado. Depreferência, coloque a declaração logo abaixo do

private var adapterDataList: List<Any> = emptyList()

. getItemCount()Firmar em seguida, de ninguna manera

override fun getItemCount(): Int 
return adapterDataList.size

`já retorne o tamanho dessa lista.

fun setList(list: List<Any>) 
adapterDataList = list
notifyDataSetChanged()

E também, já crie o método para inserir dados na lista quando o adapter já estiver instanciado.

11º Paso List<*>Para diferenciar os tipos de dados, pode utilizarliteralmente qualquer tipo de dado.suero Int`, uh String`, uh data class`ou qualquer outro que você tenha criado com uma data class`. Para este exemplo ficar mais simples, eu optei por criar 3

`, mas acaba sendo bem intuitivo para você mesmo modificar depois. data classEntão, crie os 3 `, DataAdapterForo de clase

data class Header(val id: Int)
data class Question(val id: Int)
data class Button(val id: Int)
class DataAdapter(
private val context: Context
) : RecyclerView.Adapter<DataAdapter.BaseViewHolder<*>>()
...

`para poder ser importada por outras class. Os atributos nesse exeplo são inrelevantes, então todos vão conter apenas um ID. getItemViewType(position: Int): IntE então, agora vamos a utilizar o método

.

Como diseña de antemano, o RecyclerView identifica o tipo de dado del elemento utilizando um número inteiro, então vamos a identificar ele nesta função atribuindo um inteiro utilizando como constantes. onCreateViewHolderEste método recebe a posição do item atual e retorna o tipo dele com um inteiro para a método que é chamado após ele, o

, que define el diseño que será atribuido a ViewHolder.

override fun getItemViewType(position: Int): Int 
return when (adapterDataList[position])
is Header -> TYPE_HEADER
is Question -> TYPE_QUESTION
is Button -> TYPE_BUTTON
else -> throw IllegalArgumentException("Tipo inválido na posição $position")

Agora, vamos a verificar qual o tipo de dado da seguinte forma:

Caso o dado não seja nenhum dos que precisamos, vai disparar uma exceção.

12º Paso onCreateViewHolderAntes de informar quais são os layout que serão mostrados em tela com a função

, vamos a criar os arquivos para cada um. Ejemplo de Estes são os arquivos que criei para este:

<?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_height="wrap_content"
android:elevation="2dp"
android:layout_marginBottom="30dp">

<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="142dp"
android:background="@color/purple_700"

app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>

<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Título"
android:textSize="40sp"
android:textColor="@color/white"
android:layout_marginTop="24dp"
android:layout_marginStart="24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>

<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/smile"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="#C32C2C"
android:layout_gravity="center_vertical"
android:layout_marginEnd="24dp"

app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

header_item.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_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp">

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="#D8D8D8"
android:orientation="vertical"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">

<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/enunciated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:textColor="#000"
android:textSize="16sp"
android:text="Titulo da pergunta" />

<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#FFF"
android:layout_marginStart="4dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="4dp" />

<RadioGroup
android:id="@+id/optionsGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Opção 1"/>

<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Opção 2"/>

<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Opção 3"/>

</RadioGroup>

</androidx.appcompat.widget.LinearLayoutCompat>

</androidx.constraintlayout.widget.ConstraintLayout>

question_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_marginVertical="30dp" >

<com.google.android.material.button.MaterialButton
android:id="@+id/btnSend"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="ENVIAR" />

</androidx.appcompat.widget.LinearLayoutCompat>

button_item.xml onCreateViewHolder Agora que temos os 3 layout, vamos definir o método BaseViewHolderpara inflar os layout respectivamente, que vai retornar uma instancia da classe BaseViewHolder.

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BaseViewHolder<*>
return when (viewType)
TYPE_HEADER ->
val view = LayoutInflater.from(context)
.inflate(
R.layout.header_item,
parent,
false
)
HeaderViewHolder(view)

TYPE_QUESTION ->
val view = LayoutInflater.from(context)
.inflate(
R.layout.question_item,
parent,
false
)
QuestionViewHolder(view)

TYPE_BUTTON ->
val view = LayoutInflater.from(context)
.inflate(
R.layout.button_item,
parent,
false
)
ButtonViewHolder(view)

else -> throw IllegalArgumentException("TIPO INVÁLIDO")

, e com isso vamos retornar o ViewHolder específico de cada tipo, pois eles extendem da onBindViewHolderO próximo método que vai ser ejecutado vai ser o bind , e é neste método que vamos chamar o método Any dos ViewHolders para passar o dado hacer item atual. E como o tipo de dado da list é

override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) 
val element = adapterDataList[position]

when (holder)
is HeaderViewHolder -> holder.bind(element as Header)
is QuestionViewHolder -> holder.bind(element as Question)
is ButtonViewHolder -> holder.bind(element as Button)
else -> throw IllegalArgumentException("TIPO INVÁLIDO")

eo ViewHolder precisa de um tipo específico, temos que passar de acordo ao que o ViewHolder seeka, e por isso fazemos uma conversão de tipo (cast) para o correto em cada caso. HeaderViewHolderE quando este método para ejecutado, ele vai ejecutar uma das funções QuestionViewHolder , ButtonViewHolder Europa

Lo más importante en tela o diseño específico cada um.

13º Paso DataAdapterAgora basta apenas instanciar una clase setList`, sin RecyclerView na MainActivity, defina un» adaptador «anterior como esta instancia e chamar o método

class MainActivity : AppCompatActivity() 

private val dataAdapter: DataAdapter by lazy
DataAdapter(this)

override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val recyclerView = findViewById<RecyclerView>(R.id.recycler)
recyclerView.adapter = dataAdapter
dataAdapter.setList(
listOf(
Header(0),
Question(0),
Question(1),
Question(2),
Question(3),
Question(4),
Button(0)
)
)

`, para informar os dados. Sin fim desse processo, una MainActivity vai estar asim:

Implementer o RecyclerView com múltiplos layouts é simples, mas é importante entendre o processo que leva isso acontecer. Para ver el resultado pronto, no hay repositorio ni inicio del artículo. Espero Te Ajudado!

Publicaciones relacionadas

Deja una respuesta

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

Botón volver arriba