martes, 17 de noviembre de 2015

RecyclerView - Ejemplo.

RecyclerView es un Widget o componente introducido en la librería de soporte v7, junto con otro componente llamado CardView. Aunque es considerado como un ListView avanzado, realmente es un componente muy flexible que permite mostrar colecciones de datos.

De forma predefinida tenemos 3 opciones:


  • LinearLayoutManager
  • GridLayoutManager
  • StaggeredGridLayoutManager
Obviamente se podría implementar un Layout Manager personalizado.

Para implementar un RecyclerView son imprescindibles las siguientes Clases:
  • Un Adaptador, que provee los datos y tiene la responsabilidad de la creación de views
  • Un ViewHolder, que contiene referencias a todos los views.
  • Un LayoutManager, responsable de la disposición del contenedor.
Opcionalmente es posible sobrescribir las clases ItemDecoration e ItemAnimator para modificar su comportamiento por defecto.

Lo primero es necesario añadir la dependencia a recyclerview, en archivo build.gradle del módulo app, no del proyecto.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.android.support:recyclerview-v7:21.0.+'
}

Creación de las vistas:

Cada elemento del RecyclerView tendrá una vista personalizada, en el ejemplo simplemente se compone de una imagen y un texto, por lo es necesario crear un layout que voy a llamar item.xml en la carpeta res/layout

 item.xml 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/ivItem"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="Large Text"
        android:id="@+id/tvItem" />
</LinearLayout>




Seguimos creando las vistas y ahora es necesario añadir el Widget RecyclerView al layout de la activity, por el momento no disponemos en él en la paleta de Widget, por lo que hay que añadirlo mediante texto:

 activity_main.xml 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

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

</RelativeLayout>

Nota: Android Studio no soporta la pre-visualización por el momento.

Es momento de comenzar con el código java, lo primero que necesito es una clase que me permita almacenar objetos con las propiedades que necesite, como cada elemento del RecyclerView tiene una imagen y un texto, por lo que mi clase Item tendrá esas propiedades:

 src/main/java/Item.java 
import android.graphics.drawable.Drawable;

public class Item {

    private Drawable imageSrc;
    private String name;

    public Item(Drawable imageSrc, String name) {
        this.imageSrc = imageSrc;
        this.name = name;
    }

    public Drawable getImageSrc() {
        return imageSrc;
    }

    public void setImageSrc(Drawable imageSrc) {
        this.imageSrc = imageSrc;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

El adaptador y el ViewHolder:
El adaptador se encarga de gestionar el modelo de datos y adaptarlos a cada elemento o item. Es necesario que extienda de la clase RecyclerView.Adapter. 
Destacan dos métodos:

  • onCreateViewHolder, encargado de construir las views y de crear una instancia a la clase ViewHolder.
  • onBindViewHolder, encargado de asignar los datos individualmente a cada view.
El ViewHolder es una clase interna que almacena las referencias a las views, por lo que evitamos así utilizar el método findViewById() en el adaptador, mejorando mucho el rendimiento.

He añadido los métodos add() y remove() para añadir elementos o eliminarlos, cada vez que se produzca un evento de este tipo, se notificará al adaptador de que se ha producido un cambio y cual ha sido con notifyItemInserted() o notifyItemRemoved(), existen otros métodos similares para cuando se modifica un elemento, se cambia de posición, etc...

Es muy importante sobrescribir el método getItemCount() para indicar el tamaño de la colección de datos.


 src/main/java/MyAdapter.java 
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;

public class MyAdapter extends RecyclerView.Adapter{

    private List mDataset;
    private Context context;

    public MyAdapter(Context c) {
        this.context = c;
        mDataset = new ArrayList();
    }

    public void add(Item i) {
        mDataset.add(i);
        notifyItemInserted(mDataset.indexOf(i));
    }
    public void remove(Item item) {
        int position = mDataset.indexOf(item);

        if(position != -1) {
            mDataset.remove(position);
            notifyItemRemoved(position);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {

        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final Item item = mDataset.get(position);
        holder.imageView.setImageDrawable(item.getImageSrc());
        holder.mTextView.setText(item.getName());

        holder.mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                remove(item);
            }
        });
    }

    @Override
    public int getItemCount() {
        return mDataset.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        protected ImageView imageView;
        protected TextView mTextView;

        public ViewHolder(View v) {
            super(v);
            imageView = (ImageView) v.findViewById(R.id.ivItem);
            mTextView = (TextView) v.findViewById(R.id.tvItem);
        }
    }
}

El código de la Activity :
La Activity principal es donde vamos a indicar mediante el LayoutManager, del que hablé al principio del artículo, cómo será la disposición de los elementos.
Si la colección de datos va a ser fija, es recomendable decírselo a nuestro RecyclerView para mejorar el rendimiento mediante el método setHasFixedSice(true).
Si quieres mostrar los elementos de forma horizontal, simplemente hay que añadir al código:
mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

 MainActivity.java 

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends Activity implements View.OnClickListener {

    private RecyclerView mRecyclerView;
    private MyAdapter mAdapter;
    private LinearLayoutManager mLayoutManager;


    private EditText ETaddText;
    private Button BTaddText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ETaddText = (EditText) findViewById(R.id.editTextAdd);
        BTaddText = (Button) findViewById(R.id.buttonAdd);
        BTaddText.setOnClickListener(this);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        /*
        * Si la colección de datos tiene un tamaño fijo utiliza:
        *  mRecyclerView.setHasFixedSize(true);
        *  para mejorar el rendimiento
        * */

        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        mAdapter = new MyAdapter(this);
        mRecyclerView.setAdapter(mAdapter);

        //Precarga de datos de ejemplo
        for (int i = 0; i < 5; i++) {
            mAdapter.add(new Item(getResources().getDrawable(R.mipmap.ic_launcher), "Texto: " +i));
        }
    }

    @Override
    public void onClick(View v) {
        String texto = ETaddText.getText().toString();
        mAdapter.add(new Item(getResources().getDrawable(R.mipmap.ic_launcher), texto));
    }
}

El resultado:


7 comentarios: