Implementando «Pull To Refresh» en RecyclerView

La mayoría de las aplicaciones modernas como Facebook o Twitter cuentan con la funcionalidad «Pull to Refresh», esto es, que nos permiten actualizar el contenido tirando hacia abajo de la pantalla. Anteriormente para implementar esta funcionalidad debíamos utilizar librerías de terceros, pero ahora contamos con el componente SwipeRefreshLayout, que se introdujo recientemente en la SupportLibrary.

Requerimientos

Para utilizar este componente debemos primero agregar las dependencias en nuestro archivo de configuración app/build.gradle

apply plugin: 'com.android.application'

//...

dependencies {
    // ...
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

Paso 1: Colocar los componentes

Incluímos el RecyclerView dentro del componente SwipeRefreshLayout. Recordemos que SwipeRefreshLayout es un ViewGroup que únicamente puede contener una vista scrollable como hijo.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout 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:id="@+id/swipeContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView android:id="@+id/rvItems"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" />

</android.support.v4.widget.SwipeRefreshLayout>

Ahora crearemos el layout que mostrará los items del RecyclerView.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/tvText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="5dp"
        android:text="tvText"
        android:textColor="#000000"
        android:textSize="20sp" />

</LinearLayout>

Paso 2: Agregar el RecyclerView.Adapter

Para personalizar los elementos a mostrar en un RecyclerView hemos de usar un adaptador.

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {

    private List<String> mData;
    private LayoutInflater mInflater;
    private ItemClickListener mClickListener;

    MyRecyclerViewAdapter(Context context, List<String> data) {
        this.mInflater = LayoutInflater.from(context);
        this.mData = data;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.row_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String animal = mData.get(position);
        holder.myTextView.setText(animal);
    }

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

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        TextView myTextView;

        ViewHolder(View itemView) {
            super(itemView);
            myTextView = itemView.findViewById(R.id.tvText);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
        }
    }

    String getItem(int id) {
        return mData.get(id);
    }

    void setClickListener(ItemClickListener itemClickListener) {
        this.mClickListener = itemClickListener;
    }

    public interface ItemClickListener {
        void onItemClick(View view, int position);
    }
}

Paso 3: La parte divertida

A continuación, debemos configurar SwipeRefreshLayout durante la inicialización de la actividad. La actividad que crea una instancia de SwipeRefreshLayout debe agregar un OnRefreshListener para que se le notifique cada vez que se complete el gesto del Scroll para actualizar.

El componente SwipeRefreshLayout notificará al componente que haya implementado el Listener cada vez que se complete el gesto nuevamente; el componente Listener es responsable de determinar correctamente cuándo iniciar realmente una actualización de su contenido.

public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {

    private SwipeRefreshLayout swipeContainer;
    private RecyclerView recyclerView;
    private MyRecyclerViewAdapter adapter;
    private ArrayList<String> animalNames;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // referenciamos los componentes
        swipeContainer = findViewById(R.id.swipeContainer);
        recyclerView = findViewById(R.id.rvItems);

        // agregamos datos de ejemplo
        animalNames = new ArrayList<>();
        animalNames.add("Caballo");
        animalNames.add("Vaca");
        animalNames.add("Camello");
        animalNames.add("Nicolás Maburro");
        animalNames.add("Cabra");

        // configuramos el RecyclerView
        recyclerView = findViewById(R.id.rvItems);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyRecyclerViewAdapter(this, animalNames);
        adapter.setClickListener(this);
        recyclerView.setAdapter(adapter);

        // configuramos el SwipeRefreshLayout
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                fetchTimelineAsync();
            }
        });

        swipeContainer.setColorSchemeResources(android.R.color.holo_blue_bright,
                android.R.color.holo_green_light,
                android.R.color.holo_orange_light,
                android.R.color.holo_red_light);
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(this, "Has tocado " + adapter.getItem(position) + " en la fila " + position, Toast.LENGTH_SHORT).show();
    }

    public void fetchTimelineAsync() {
        new LongRunningTask().execute();
    }

    private class LongRunningTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            try {
                animalNames.add("New item " + (animalNames.size() + 1));

                Thread.sleep(3000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);

            adapter.notifyDataSetChanged();
            swipeContainer.setRefreshing(false);
        }
    }
}

Nota: no debemos olvidar indicarle al componente SwipeRefreshLayout que hemos completado la tarea con la línea swipeContainer.setRefreshing(false);

Conclución

Como hemos podido observar, el componente SwipeRefreshLayout es muy fácil de utilizar y añade una funcionalidad de lujo a nuestra aplicación. Si deseas descargar el código de ejemplo puedes hacerlo desde aquí.

Deja un comentario

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