El widget de RecyclerView en Android sirve para crear una lista de elementos de cualquier tipo, definiendo un layout personalizado o propio para mostrarlos, y permitiéndonos una mayor personalización.
En este post mostraré un ejemplo de Android y RecyclerView con:
El código completo se encuentra en mi GitHub, y la aplicación de demostración en la página de releases.
En la vista principal tenemos un elemento de RecyclerView
y los elementos para interactuar con el mismo, es decir, dos botones y 3 campos de texto.
<?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="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewMascotas"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.405"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnAgregar" />
<Button
android:id="@+id/btnAgregar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="Agregar"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editTextEdad" />
<Button
android:id="@+id/btnEliminar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="Quitar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/editTextIndice" />
<EditText
android:id="@+id/editTextNombre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ems="10"
android:hint="Nombre"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<EditText
android:id="@+id/editTextEdad"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ems="10"
android:hint="Edad"
android:inputType="number"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editTextNombre" />
<EditText
android:id="@+id/editTextIndice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ems="10"
android:hint="Índice"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="parzibyte.me/blog"
android:textSize="26sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Por el momento no veremos el código Java, lo veremos cuando tengamos el adaptador terminado.
Dentro de nuestro RecyclerView mostraremos una fila personalizada con datos personalizados. En este caso usaremos una mascota que tiene nombre
y edad
así que definimos el layout XML:
Así será la vista, y se cambiará según el adaptador y el elemento actual que el usuario esté viendo dentro del RecyclerView. El código de la vista de arriba es el siguiente:
<?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="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<TextView
android:id="@+id/textViewNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Nombre" />
<TextView
android:id="@+id/textViewEdad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewNombre"
tools:text="Edad" />
</androidx.constraintlayout.widget.ConstraintLayout>
Para inflar esta vista debemos declarar un ViewHolder
que se encargará de obtener referencias a los elementos de la vista, es decir, a cada TextView
. Cabe mencionar que aparte de un TextView podemos tener cualquier tipo de vista.
El código del ViewHolder
es el siguiente:
package me.parzibyte.demostracionrecyclerview;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
class ViewHolderMascota extends RecyclerView.ViewHolder {
private TextView textViewEdad, textViewNombre;
ViewHolderMascota(@NonNull View itemView) {
super(itemView);
textViewEdad = itemView.findViewById(R.id.textViewEdad);
textViewNombre = itemView.findViewById(R.id.textViewNombre);
}
TextView getTextViewEdad() {
return textViewEdad;
}
TextView getTextViewNombre() {
return textViewNombre;
}
}
Fíjate en que estamos extendiendo de RecyclerView.ViewHolder
. Además de eso, he añadido dos métodos para obtener los TextView
, esto lo hago para colocar los datos de la mascota dentro de cada fila como veremos más adelante.
Y finalmente, la clase Mascota
a continuación:
package me.parzibyte.demostracionrecyclerview;
public class Mascota {
private String nombre;
private int edad;
public Mascota(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
public String getNombre() {
return nombre;
}
public int getEdad() {
return edad;
}
@Override
public String toString() {
return "Mascota{" +
"nombre='" + nombre + '\'' +
", edad=" + edad +
'}';
}
}
Con eso ya tenemos definido cada elemento que irá dentro del RecyclerView, con el ViewHolder inflamos el layout XML.
Ahora vamos a ver lo realmente bueno de Android y RecyclerView. Para poder usar un RecyclerView
necesitamos un adaptador que se encargue de mostrar los datos y de administrarlos.
Este adaptador debe extender de RecyclerView.Adapter
y debe ser de un tipo de ViewHolder
, en este caso será de tipo de ViewHolder
de ViewHolderMascota
como vimos anteriormente. Debemos sobrescribir los siguientes métodos:
onCreateViewHolder
: aquí inflamos la vista, todavía no renderizamos datosonBindViewHolder
: aquí tenemos la vista anteriormente inflada, además de la posición; con ella obtenemos el elemento de nuestra lista y colocamos el texto de los TextView según sea el caso.getItemCount
: debemos regresar el total de elementos que hay.Normalmente tendremos un ArrayList
o lista dentro de nuestro adaptador, y por cada elemento de la lista tendremos una layout y un ViewHolder
que vimos anteriormente.
@NonNull
@Override
public ViewHolderMascota onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// Esta es la vista del layout que muestra los detalles de la mascota (fila_mascota.xml)
View vista = LayoutInflater.from(parent.getContext()).inflate(R.layout.fila_mascota, parent, false);
// Crear el viewholder a partir de esta vista. Mira la clase ViewHolderMascota si quieres
final ViewHolderMascota viewHolder = new ViewHolderMascota(vista);
// En el click de la vista (la mascota en general) invocamos a nuestra interfaz personalizada pasándole la vista y la mascota
vista.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
interfazClickRecyclerView.onClick(v, mascotas.get(viewHolder.getAdapterPosition()));
}
});
return viewHolder;
}
Primero inflamos la vista de fila_mascota
, es decir, el archivo de layout que vimos antes. Después creamos un ViewHolder
de la mascota, en donde definimos los elementos de la vista (los TextView).
En la línea 9 agregamos un listener para el click de ese elemento, todavía no veremos esa parte pero presta atención; simplemente estamos pasándole (al listener) la vista y la mascota que fue tocada.
Finalmente regresamos el ViewHolder
después de haber colocado el listener.
En esta función se nos pasa la posición que se va a dibujar; con ella obtenemos la mascota y establecemos los datos:
@Override
public void onBindViewHolder(@NonNull ViewHolderMascota holder, int position) {
// Dibujar la fila de la mascota con los datos de la mascota actualmente solicidata según la variable position
Mascota mascota = this.mascotas.get(position);
holder.getTextViewEdad().setText(String.valueOf(mascota.getEdad()));
holder.getTextViewNombre().setText(mascota.getNombre());
}
Con la variable position
obtenemos la mascota, después obtenemos los TextView
del holder y le ponemos el texto correspondiente.
Como ArrayList ya tiene un método que cuenta sus elementos, simplemente regresamos lo que regrese size
:
@Override
public int getItemCount() {
return this.mascotas.size();
}
Además de los métodos que se deben sobrescribir, he agregado otros métodos para trabajar con el ArrayList. Veamos el código a continuación:
package me.parzibyte.demostracionrecyclerview;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class AdaptadorRecyclerView extends RecyclerView.Adapter<ViewHolderMascota> {
private List<Mascota> mascotas;
private InterfazClickRecyclerView interfazClickRecyclerView;
public AdaptadorRecyclerView(List<Mascota> mascotas) {
this.mascotas = mascotas;
}
public List<Mascota> getMascotas() {
return mascotas;
}
public void setMascotas(List<Mascota> mascotas) {
this.mascotas = mascotas;
this.notifyDataSetChanged();
}
public AdaptadorRecyclerView(InterfazClickRecyclerView interfazClickRecyclerView) {
this.interfazClickRecyclerView = interfazClickRecyclerView;
this.mascotas = new ArrayList<>();
}
public void agregarMascota(Mascota mascota) {
this.mascotas.add(mascota);
this.notifyItemInserted(this.mascotas.size() - 1);
}
public void eliminar(int indice) {
if (indice < 0 || indice >= this.getItemCount()) return;
this.mascotas.remove(indice);
this.notifyItemRemoved(indice);
}
@NonNull
@Override
public ViewHolderMascota onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// Esta es la vista del layout que muestra los detalles de la mascota (fila_mascota.xml)
View vista = LayoutInflater.from(parent.getContext()).inflate(R.layout.fila_mascota, parent, false);
// Crear el viewholder a partir de esta vista. Mira la clase ViewHolderMascota si quieres
final ViewHolderMascota viewHolder = new ViewHolderMascota(vista);
// En el click de la vista (la mascota en general) invocamos a nuestra interfaz personalizada pasándole la vista y la mascota
vista.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
interfazClickRecyclerView.onClick(v, mascotas.get(viewHolder.getAdapterPosition()));
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolderMascota holder, int position) {
// Dibujar la fila de la mascota con los datos de la mascota actualmente solicidata según la variable position
Mascota mascota = this.mascotas.get(position);
holder.getTextViewEdad().setText(String.valueOf(mascota.getEdad()));
holder.getTextViewNombre().setText(mascota.getNombre());
}
@Override
public int getItemCount() {
return this.mascotas.size();
}
}
Tenemos al método agregarMascota
y eliminar
, que simplemente trabaja con el ArrayList. Es muy importante que cada que hagamos esto, notifiquemos que hemos cambiado los datos para que el adaptador sepa y para que se muestren las animaciones.
Esto se logra invocando a notifyDataSetChanged
, notifyItemRemoved
y notifyItemInserted
. Por cierto, tenemos un constructor que recibe una interfaz de un listener, esto es para comunicar el RecyclerView con la actividad. En unos momento veremos eso en profundidad.
Para saber cuando hacen click en un elemento del RecyclerView y obtener al misma, debemos crear un listener. En este caso dentro del adaptador estamos invocando al listener:
// En el click de la vista (la mascota en general) invocamos a nuestra interfaz personalizada pasándole la vista y la mascota
vista.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
interfazClickRecyclerView.onClick(v, mascotas.get(viewHolder.getAdapterPosition()));
}
});
La interfaz es muy simple:
package me.parzibyte.demostracionrecyclerview;
import android.view.View;
public interface InterfazClickRecyclerView {
public void onClick(View v, Mascota m);
}
Cuando se implemente, solo hay que sobrescribir al método onClick
y recibiremos la mascota junto con la vista. La vista no es necesaria en estos casos pero siempre es bueno pasarla por si se hace algo con ella.
Ahora sí, dentro de la actividad principal creamos el adaptador, instanciamos el RecyclerView
y agregamos datos. Primero veamos cómo instanciamos el adaptador que recibe la interfaz:
final AdaptadorRecyclerView adaptadorRecyclerView = new AdaptadorRecyclerView(new InterfazClickRecyclerView() {
@Override
public void onClick(View v, Mascota m) {
Toast.makeText(MainActivity.this, m.toString(), Toast.LENGTH_SHORT).show();
}
});
Estamos definiendo la interfaz al crear una instancia de AdaptadorRecyclerView
, sobrescribimos el onClick
y simplemente mostramos en un Toast
a la mascota.
Ahora veamos cómo juntamos el RecyclerView
con el Adaptador:
// Configuramos cómo se van a organizar las vistas dentro del RecyclerView; simplemente es un LinearLayout para que
// aparezcan una debajo de otra
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this);
recyclerViewMascotas.setLayoutManager(linearLayoutManager);
// La línea que divide los elementos
recyclerViewMascotas.addItemDecoration(new DividerItemDecoration(MainActivity.this, LinearLayoutManager.VERTICAL));
// El adaptador que se encarga de toda la lógica
recyclerViewMascotas.setAdapter(adaptadorRecyclerView);
Primero debemos definir cómo se colocarán los elementos o filas personalizadas dentro del RecyclerView
; aunque cada fila está definida con un ConstraintLayout
, se deben organizar todas dentro de la lista. Así que indicamos que se organizarán con un LinearLayoutManager
para que aparezcan en forma de lista.
Después agregamos una decoración para poner un separador o línea entre cada elemento.
Finalmente invocamos a setAdapter
y pasamos el adaptador creado anteriormente.
Para agregar elementos simplemente invocamos al método agregarMascota
del adaptador, y le pasamos una nueva mascota:
btnAgregar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String nombre = editTextNombre.getText().toString();
String edad = editTextEdad.getText().toString();
if (nombre.isEmpty() || edad.isEmpty()) {
Toast.makeText(MainActivity.this, "Rellena los campos", Toast.LENGTH_SHORT).show();
return;
}
adaptadorRecyclerView.agregarMascota(new Mascota(nombre, Integer.valueOf(edad)));
}
});
Estamos haciendo una validación muy básica, si quieres una con más estilo mira este post.
Recuerda que dentro del adaptador también tenemos al método setMascotas
, esto es por si ya tienes una lista obtenida, por ejemplo, de una base de datos.
Por cierto, aunque no se ve a simple vista, existe una animación al agregar el elemento.
Para eliminar un elemento del RecyclerView
invocamos a eliminar
pasando el índice.
btnEliminar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String indice = editTextIndice.getText().toString();
if (indice.isEmpty()) {
Toast.makeText(MainActivity.this, "Escribe el índice", Toast.LENGTH_SHORT).show();
return;
}
adaptadorRecyclerView.eliminar(Integer.valueOf(indice));
}
});
Ahora sí se puede apreciar más la animación al remover el elemento.
Finalmente veamos en acción el Toast
que aparece cuando se toca un elemento:
Finalmente así queda todo el código, ya que hace un momento omití la declaración de elementos y esas cosas:
package me.parzibyte.demostracionrecyclerview;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnAgregar = findViewById(R.id.btnAgregar);
Button btnEliminar = findViewById(R.id.btnEliminar);
RecyclerView recyclerViewMascotas = findViewById(R.id.recyclerViewMascotas);
final EditText editTextNombre = findViewById(R.id.editTextNombre);
final EditText editTextEdad = findViewById(R.id.editTextEdad);
final EditText editTextIndice = findViewById(R.id.editTextIndice);
final AdaptadorRecyclerView adaptadorRecyclerView = new AdaptadorRecyclerView(new InterfazClickRecyclerView() {
@Override
public void onClick(View v, Mascota m) {
Toast.makeText(MainActivity.this, m.toString(), Toast.LENGTH_SHORT).show();
}
});
// Configuramos cómo se van a organizar las vistas dentro del RecyclerView; simplemente es un LinearLayout para que
// aparezcan una debajo de otra
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(MainActivity.this);
recyclerViewMascotas.setLayoutManager(linearLayoutManager);
// La línea que divide los elementos
recyclerViewMascotas.addItemDecoration(new DividerItemDecoration(MainActivity.this, LinearLayoutManager.VERTICAL));
// El adaptador que se encarga de toda la lógica
recyclerViewMascotas.setAdapter(adaptadorRecyclerView);
// adaptadorRecyclerView.agregarMascota(new Mascota());
btnAgregar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String nombre = editTextNombre.getText().toString();
String edad = editTextEdad.getText().toString();
if (nombre.isEmpty() || edad.isEmpty()) {
Toast.makeText(MainActivity.this, "Rellena los campos", Toast.LENGTH_SHORT).show();
return;
}
adaptadorRecyclerView.agregarMascota(new Mascota(nombre, Integer.valueOf(edad)));
}
});
btnEliminar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String indice = editTextIndice.getText().toString();
if (indice.isEmpty()) {
Toast.makeText(MainActivity.this, "Escribe el índice", Toast.LENGTH_SHORT).show();
return;
}
adaptadorRecyclerView.eliminar(Integer.valueOf(indice));
}
});
}
}
Parece complejo, pero esto nos da un gran poder sobre cómo mostramos los datos, ya que tenemos posibilidades infinitas para cada elemento, es decir, tenemos un diseño personalizado por cada vista, y podemos agregar listeners de igual manera personalizados.
Recuerda que puedes descargar el APK aquí, y ver el código completo aquí.
Si quieres un ejemplo de RecyclerView con base de datos mira este post.
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…
Esta web usa cookies.