Creando un RecyclerView con Kotlin en Android Studio

- 👤 Andrés Cruz

🇺🇸 In english

Creando un RecyclerView con Kotlin en Android Studio

Descargar

Los RecyclerView al igual que los GridView y ListView, permite crear listados de ítems ya sea a través de listas o celdas; los RecyclerView pueden verse como una versión más flexible, potente y actualizada que estos y seguramente en algún momento serán el reemplazo definitivo de los mismos; la forma de funcionamiento es la misma empleada que sus predecesores como puedes ver en esta imagen:

Esquema del Recyclerview

Adapter que actúa como puente entre los datos y la vista.

Agregando la librería de soporte para trabajar con los RecyclerViews y CardView

De la misma forma que agregamos las dependencias necesarias para agregar múltiples elementos de la Librería de Soporte (en específico el FloatActionButton), es necesario agregar las dependencias para poder emplear el RecyclerView y CardView en nuestro proyecto; agrega las siguientes dependencias en el archivo build.gradle:

compile 'com.android.support:cardview-v7:23.0.1'
compile 'com.android.support:recyclerview-v7:23.0.1'

app/build.gradle

Los CardView

El RecyclerView y los CardView forman parte de la librería de soporte; los CardView heredan de los ViewGroups más directamente de los FrameLayout y por ende es un elemento que nos permite definir muchas otros elementos dentro del mismo como por ejemplo los siguientes:

<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
   android:id="@+id/card_view"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="center"
   android:layout_margin="2dp"
   card_view:cardCornerRadius="1dp">
   <RelativeLayout
       android:id="@+id/parent_body_rl"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="#FF5722"
       android:orientation="vertical"
       android:padding="2dp">
       <LinearLayout
           android:id="@+id/parent_body_ll"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:orientation="horizontal"
           android:padding="2dp">
           <LinearLayout
               android:id="@+id/color_ll"
               android:layout_width="50dp"
               android:layout_height="50dp"
               android:layout_gravity="center"
               android:layout_margin="10dp"
               android:background="#FF0000"
               android:orientation="vertical" />
           <LinearLayout
               android:layout_width="0dp"
               android:layout_height="wrap_content"
               android:layout_weight="1"
               android:orientation="vertical"
               android:padding="2dp">
               <TextView
                   android:id="@+id/name_tv"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:padding="10dp"
                   android:text="texto 1"
                   android:textColor="#FFFFFF"
                   android:textSize="25sp" />
               <TextView
                   android:id="@+id/description_tv"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:layout_below="@+id/info_text"
                   android:padding="10dp"
                   android:text="texto 2"
                   android:textColor="#FFFFFF"
                   android:textSize="15sp" />
           </LinearLayout>
       </LinearLayout>
   </RelativeLayout>
</android.support.v7.widget.CardView>

Obteniendo el siguiente resultado:

Ejemplo de cardView en android

En otras palabras, podemos emplear los CardView en conjunto con los RecyclerView; en donde el CardView define los ítems del listado.

Creando un RecyclerView

Antes que nada, necesitas agregar un elemento RecyclerView en el layout de nuestra Activity o Fragment:

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"
    />

Al igual que cualquier otro elemento, para referenciar el RecyclerView anterior desde nuestra Activity empleamos el siguiente código Java:

RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);

Especificando el posicionamiento del RecyclerView con Layout Manager

A partir de aquí podemos apreciar algunos cambios con respecto al momento de crear los ListView y GridView; para definir el posicionamiento en que deseamos que renderice nuestra lista de ítems (en forma de listas o celdas):

gridview listview en android

Empleamos el siguiente código Java para que los elementos se posicionan en la lista a través de listados:

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(LinearLayoutManager);

O el GridLayoutManager para que los elementos se posicionan en la lista a través de de celdas:

GridLayoutManager gridLayoutManager = new GridLayoutManager(context); recyclerView.setLayoutManager(gridLayoutManager);

Especificando los datos y el modelo

Para mayor facilidad al momento de manipular los datos crearemos un modelo Person para especificar una lista con nuestros datos que pasaremos posteriormente al Adapter.

public class Person {
   String name;
   String description;
   String color;
   Person(String name, String description,String color){
       this.name = name;
       this.description = description;
       this.color = color;
   }
   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public String getDescription() {
       return description;
   }
   public void setDescription(String description) {
       this.description = description;
   }
   public String getColor() {
       return color;
   }
   public void setColor(String color) {
       this.color = color;
   }
}

Nada fuera de lo común, la persona costa de un nombre, una descripción (edad, etc) y el color favorito.

Definiendo el Adapter

El Adapter que utiliza los RecyclerView son muy parecidos a los empleados por los ListView y los GridView en lo que a su estructura y comportamiento se refiere; además de esto, vamos a emplear los ViewHolder para referencias más fácilmente los elementos que nos interese de nuestro listado; la definición del Adapter a continuación:

public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder>{
   private ArrayList<Person> persons;
   // Provee una referencia a cada item dentro de una vista y acceder a ellos facilmente
   public static class ViewHolder extends RecyclerView.ViewHolder {
       // Cada uno de los elementos de mi vista
       public TextView nameTextView,descriptionTextView;
       public CardView cardView;
       public LinearLayout colorLl;
       public RelativeLayout parentBodyRl;
      
       public ViewHolder(View v) {
           super(v);
           parentBodyRl = (RelativeLayout) v.findViewById(R.id.parent_body_rl);
           cardView = (CardView) v.findViewById(R.id.card_view);
           nameTextView = (TextView) v.findViewById(R.id.name_tv);
           descriptionTextView = (TextView) v.findViewById(R.id.description_tv);
           colorLl = (LinearLayout) v.findViewById(R.id.color_ll);
       }
   }
   // Constructor
   public ListAdapter(ArrayList<Person> persons) {
       this.persons = persons;
   }
   // Create new views (invoked by the layout manager)
   @Override
   public ListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                         int viewType) {
       // inflo la vista (vista padre)
       View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_adapter, parent, false);
       // creo el grupo de vistas
       ViewHolder vh = new ViewHolder(v);
       return vh;
   }
   // Reemplaza en contenido de la vista
   @Override
   public void onBindViewHolder(ViewHolder viewHolder, final int position) {
       viewHolder.nameTextView.setText(persons.get(position).getName());
       viewHolder.descriptionTextView.setText(persons.get(position).getDescription());
   }
   // Retorna el tamano de nuestra data
   @Override
   public int getItemCount() {
       return persons.size();
   }
}

Como vemos en el código anterior, hay tres métodos que debemos sobreescribir:

El método getItemCount()que debe retornar el tamaño de la lista, o lo que es lo mismo, el tamaño de nuestra lista de elementos que se mostrarán en el listado.

El método onCreateViewHolder() permite inicializar todos los elementos que componen nuestra clase ViewHolder definida anteriormente; además definimos el layout (que puede variar para cada elemento) que utiliza cada elemento. También es posible definir los eventos en esta función.

En el método onBindViewHolder() se debe definir o enlazar los valores de los distintos campos.

Invocando el Adapter anterior

Desde nuestra Activity o Fragment, creamos un objeto del Adapter definido anteriormente:

ListAdapter listAdapter = new ListAdapter(persons); audioRv.setAdapter(listAdapter);

Y al correr nuestra aplicación en el emulador o dispositivo físico:

Ejemplo final de Recyclerview

Conclusiones

En esta entrada dimos los primeros pasos con los RecyclerView y CardView disponibles en las últimas versiones de Android y en posteriores mediante la Librería de Soporte; pudimos notar que el funcionamiento y la implementación son muy parecidas a lo que estamos acostumbrados con los GridView y ListView; en siguientes entradas veremos lo sencillo que resulta de implementar otras funcionalidades a las RecyclerView y CardView como son el swipe y algunas sencillas animaciones para ocultar secciones de los CardView.

Descargar

En una anterior entrada hablamos un poco sobre el futuro (y presente) de Kotlin en el mundo del desarrollo de aplicaciones Android empleando este lenguaje de programación que poco a poco intenta ir reemplazando a Java como lenguaje primario y oficial en el desarrollo de aplicaciones Android; una de las razones que están expuestas en esa entrada que puedes consultar, el la simplificación de las distintos elementos que podemos desarrollar, un ejemplo de esto es el desarrollo de los RecyclerView de un adapter como veremos a continuación.

Para darle continuidad y soporte a algunos de los argumentos realizados en la anterior entrada, hoy veremos cómo realizar un sencillo listado de elementos mediante un RecyclerView empleando netamente Kotlin como lenguaje de programación; es decir, nada de Java.

RecyclerView en Android con Kotlin: Mismo enfoque, eventos, clases pero diferente sintaxis

Las clases a emplear para crear un listado con RecyclerView con Kotlin serán las mismas empleadas para el desarrollo de un Recyclerview empleando Java. la clase modelo, adaptador y por supuesto la actividad, también necesitaremos un par de layouts; uno para la actividad y otro para el listado que vendría siendo nuestro RecyclerView.

La clase modelo de nuestro adaptador

Aquí debemos de definir la clase o modelo que definirá nuestro objeto, si es una persona, si es un automóvil si es una computadora o en este caso definimos una clase Item que contará con un identificador y una descripción que sería de las cosas más sencillas que podemos definir:

data class Item(val id: Long, val title: String, val description: String)

La Actividad principal: la que implementa el Adapter

Una novedad que tiene Kotlin con respecto a Java es que mediante el nombre del identificador del elemento en el layout; (por ejemplo android:id="@+id/rv_item" para definir el identificador del RecyclerView en el layout de nuestra actividad) es suficiente para referenciar en la actividad o clase que establece el contenido de dicha vista, y por lo tanto en Kotlin ya no es necesario establecer el valor de dichas variables como se hace en Java-Android; esto se logra mediante un plugin/include que se importa al momento de hacer la referencia y de manera automática por el Android Studio (import kotlinx.android.synthetic.main.activity_main.*):

RecyclerView rv_item = (RecyclerView) findViewById(R.id.rv_item);

Aunque también podemos hacer la versión clásica que empleamos en Android con Java; finalmente el código de la Actividad:

import android.content.Context
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import mykotlin.dl.recyclerview.recyclerviewkotlin.adapter.ItemAdapter
import mykotlin.dl.recyclerview.recyclerviewkotlin.model.Item
import kotlin.dl.recyclerview.recyclerviewkotlin.R
class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       val items =  getLists()
       rv_item.layoutManager = LinearLayoutManager(this)
       rv_item.hasFixedSize()
       rv_item.adapter = ItemAdapter(items)
       rv_item.layoutManager = LinearLayoutManager(this)
       rv_item.adapter = ItemAdapter(items)
   }
   fun getLists(): ArrayList<Item> {
       var lists = ArrayList<Item>()
       lists.add(Item(1, "Item 1", "Descripcion 1"))
       lists.add(Item(1, "Item 2", "Descripcion 2"))
       lists.add(Item(1, "Item 3", "Descripcion 3"))
       lists.add(Item(1, "Item 4", "Descripcion 4"))
       return lists;
   }
}

La actividad anterior se encarga de definir el tipo de layout (que en este caso será listado tipo ListView) y de popular (llenar) un ArrayList de tipo Item con algunos datos de prueba y de por supuesto, crear una instancia del adaptador que creará el listado que definimos en el siguiente bloque dentro de esta entrada.

La clase adaptador: Define la lógica y comportamiento del RecyclerView

La clase adaptador, que es la que se encarga de definir el comportamiento y estilo de cada uno de los componentes del listado, como podemos ver, se realiza el mismo nombrado para sobrescribir cada uno de las funciones heredadas de la clase RecyclerView.Adapter; nuevamente empleamos el nombre de los items de cada elemento definido en la vista sin necesidad de referenciarlos primero (ej: itemView.tv_description con el import autogenerado del IDE import kotlinx.android.synthetic.main.adapter_item.view.*):

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.adapter_item.view.*
import kotlin.dl.recyclerview.recyclerviewkotlin.R
import mykotlin.dl.recyclerview.recyclerviewkotlin.model.Item
class ItemAdapter  (val userList: ArrayList<Item>) : RecyclerView.Adapter<ItemAdapter.ViewHolder>() {
  
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
       val v = LayoutInflater.from(parent.context).inflate(R.layout.adapter_item, parent, false)
       return ViewHolder(v)
   }
  
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
       holder.bindItems(userList[position])
   }
  
   override fun getItemCount(): Int {
       return userList.size
   }
  
   class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
       fun bindItems(item: Item) {
           itemView.tv_title.text=item.title
           itemView.tv_description.text=item.description
       }
   }
}

Sobreescribimos los métodos de siempre, onBindViewHolder para retornar el ítem actual getItemCount para retornar el tamaño de la colección de objetos onCreateViewHolder para especificar el layout de nuestro listado así como componer la vista y por último la clase ViewHolder que permite definir cada uno de los elementos compuestos en nuestro layout y especificar su contenido y por supuesto el método constructor en donde inicializamos los componentes fundamentales o que requiramos.

El layout de nuestra actividad

En cuanto a los layouts, se mantienen exactamente los mismos que usamos en Java-Android; más que eso no hay mucho que decir, desde nuestra actividad especificamos un elemento RecyclerView y el otro layout es el especificado por empleado por nuestro listado:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="mykotlin.dl.recyclerview.recyclerviewkotlin.MainActivity">
   <android.support.v7.widget.RecyclerView
       android:layout_width="match_parent"
       android:id="@+id/rv_item"
       android:layout_height="wrap_content">
   </android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>

El layout de nuestro listado

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="vertical">
   <TextView
       android:id="@+id/tv_title"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
   <TextView
       android:id="@+id/tv_description"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
</LinearLayout>

Como podemos ver la lógica empleada es la misma que empleamos con Java, te invito a que revises algunas de las anteriores entradas que están listadas al final de esta entrada y las compares con estas para ver la similitud que existe entre ambas.

Finalmente, obtenemos un listado como este:

Puedes darle el estilo que quieras modificando el layout o el XML de estilo como prefieras, para darle el diseño a tu gusto; la idea en esta entrada era mostrar cómo crear un RecyclerView empleando netamente Kotlin como lenguaje de programación y no centrarnos en el diseño.

Conclusión

Cómo podemos ir viendo, Kotlin es un lenguaje completo que nos permite desarrollar aplicaciones en Android más fácil (al menos en lo que a sintaxis se refiere) que con Java.

Creando un filtro para los RecycleView en Android

Creando un filtro para los RecycleView en Android

Descargar

En esta entrada veremos cómo crear un filtro para nuestros RecycleView; en una entrada anterior vimos cómo crear listados a través de listas y grids con el RecycleView hoy veremos el mismo proceso pero con un extra el cual consiste en crear un filtro para el listado anterior a través (lógicamente) por un campo de búsqueda con un simple EditText.

En nuestro adaptador crearemos una clase extra que llamaremos CustomFilter que lucirá de la siguiente manera:

public class CustomFilter extends Filter {
        private ListAdapter listAdapter;
        private CustomFilter(ListAdapter listAdapter) {
            super();
            this.listAdapter = listAdapter;
        }
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            personsFilter.clear();
            final FilterResults results = new FilterResults();
            if (constraint.length() == 0) {
                personsFilter.addAll(persons);
            } else {
                final String filterPattern = constraint.toString().toLowerCase().trim();
                for (final Person person : persons) {
                    if (person.getName().toLowerCase().contains(filterPattern)) {
                        personsFilter.add(person);
                    }
                }
            }
            results.values = personsFilter;
            results.count = personsFilter.size();
            return results;
        }
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            this.listAdapter.notifyDataSetChanged();
        }
    }

Además de agregar un implements para implementar la clase Filterable en nuestro Adapter:

public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder> implements Filterable {
    private ArrayList&lt;Person&gt; persons;
    private ArrayList&lt;Person&gt; personsFilter;
    private CustomFilter mFilter;
...
}
public ListAdapter(ArrayList&lt;Person&gt; persons) {
    this.persons = persons;
    this.personsFilter = new ArrayList&lt;&gt;();
    this.personsFilter.addAll(persons);
    this.mFilter = new CustomFilter(ListAdapter.this);
}

En nuestro adaptador y el método asociado getFilter que sobreescribimos:

@Override
public Filter getFilter() {
    return mFilter;
}

Un pequeño dato que tenemos que hacer en nuestro adaptador es crear otra lista la cual contiene la data filtrada por el usuario y otro listado que contiene el total de la data que conforma nuestro listado sin aplicar ningún filtro; teniendo esto en cuenta nuestra el constructor y así como otros métodos de control quedaran definidos de la siguiente manera:

// Constructor
public ListAdapter(ArrayList&lt;Person&gt; persons) {
	this.persons = persons;
	this.personsFilter = new ArrayList&lt;&gt;();
	this.personsFilter.addAll(persons);
	this.mFilter = new CustomFilter(ListAdapter.this);
}
...
@Override
public int getItemCount() {
	return personsFilter.size();
}
...
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
	viewHolder.nameTextView.setText(personsFilter.get(position).getName());
	viewHolder.descriptionTextView.setText(personsFilter.get(position).getDescription());
	viewHolder.colorLl.setBackgroundColor(Color.parseColor(personsFilter.get(position).getColor()));
}

Cómo ves empleamos el ArrayList filtrable en vez del ArrayList completo.

Tenemos listo el adaptador, ahora falta configurar en nuestra actividad o fragment el campo de búsqueda para poder filtrar nuestro listado y los eventos asociados; para ello emplearemos un EditText como comentamos anteriormente:

etSearchBox = (EditText) findViewById(R.id.etSearchBox);

Y el evento escuchador (listener) que se activa al ingresar/remover texto sobre el mismo:

        etSearchBox.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                listAdapter.getFilter().filter(s.toString());
            }
            @Override
            public void afterTextChanged(Editable s) {
            }
        });

Cómo puedes ver, al cambiar el texto se invoca el método filter pasando como parámetro el texto a filtrar.

Puedes especificar el funcionamiento del filtro en la clase CustomFilter del adaptador definiendo el comportamiento en el momento en el que se realiza el llenado de personsFilter al realizar la comparación:

if (person.getName().toLowerCase().contains(filterPattern))

En nuestro caso nos interesa que NO sea sensible a mayúsculas/minúsculas y que contenga (contains) la palabra o sección clave y de esta forma tener un filtro bastante flexible, pero puedes expresarlo como desees.

Descargar

Acepto recibir anuncios de interes sobre este Blog.

Los RecyclerView al igual que los GridView y ListView, permite crear listados de ítems ya sea a través de listas o celdas y son una versión más flexible, potente y actualizada que los GridView ListView.

| 👤 Andrés Cruz

🇺🇸 In english