Android

Android y RecyclerView – Tutorial

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:

  • Animación al agregar y eliminar un elemento
  • AdapterView personalizado
  • Listener personalizado (para cuando hacen click en el elemento)
  • Layout personalizado que será mostrado en el RecyclerView

El código completo se encuentra en mi GitHub, y la aplicación de demostración en la página de releases.

La vista principal

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.

He utilizado un ConstraintLayout, el código XML de esta vista es el siguiente. Presta atención al id de cada elemento.

<?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.

Elemento que se mostrará en RecyclerView

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.

El adaptador del RecyclerView

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 datos
  • onBindViewHolder: 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.

onCreateViewHolder

@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.

onBindViewHolder

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.

getItemCount

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();
}

Código completo del adaptador

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.

El listener del item del RecyclerView en Android

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.

Código de la actividad para usar Android con RecyclerView

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.

Agregar elementos a RecyclerView de Android

 

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.

Eliminar elemento de RecyclerView en Android

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.

Obtener elemento en el click | Android y RecyclerView

Finalmente veamos en acción el Toast que aparece cuando se toca un elemento:

Código completo de actividad

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));
            }
        });
    }
}

Conclusión

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.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

3 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

3 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

3 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

3 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

3 días hace

Solución: Apache – Server unable to read htaccess file

Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…

4 días hace

Esta web usa cookies.