
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);
Conclusió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í.