Cómo implementar 'Deslizar para opciones' en RecyclerView

Supongamos que un usuario de su sitio desea editar un elemento de la lista sin abrir el elemento y buscar opciones de edición. Si puede habilitar esta funcionalidad, le brinda a ese usuario una buena experiencia de usuario .

Pocket, una aplicación de marcadores propiedad de Mozilla, hace algo similar. Puede compartir / archivar / eliminar sus artículos guardados directamente de la lista sin abrir el artículo. Luego puede hacer clic en el botón de menú en la esquina superior derecha y seleccionar su opción de edición.

Así que en este tutorial intentaremos codificar este.

Esto es lo que queremos lograr :

Primero, creemos una lista RecyclerView normal

RecyclerView es una versión avanzada y flexible de ListView y GridView. Es capaz de almacenar grandes cantidades de datos de listas y tiene un mejor rendimiento que sus predecesores.

Como sugiere el nombre, RecyclerView 'recicla' los elementos de nuestra lista una vez que están fuera de la vista al desplazarse y los vuelve a llenar cuando vuelven a verse. Por lo tanto, el contenedor de la lista debe mantener solo un número limitado de vistas y no la lista completa.

Es tan flexible que la nueva clase ViewPager2, utilizada para crear pestañas deslizables, está escrita sobre RecyclerView.

Cree un POJO (objeto Java antiguo simple) para contener los datos de la lista

public class RecyclerEntity { private String title; private boolean showMenu = false; private int image; public RecyclerEntity() { } public RecyclerEntity(String title, int image, boolean showMenu) { this.title = title; this.showMenu = showMenu; this.image = image; } public int getImage() { return image; } public void setImage(int image) { this.image = image; } //... all the getters and setters }

Observe que aquí tenemos un miembro showMenu que manejará la visibilidad del menú para ese elemento de la lista en nuestro RecyclerView.

Crear un adaptador RecyclerView

public class RecyclerAdapter extends RecyclerView.Adapter { List list; Context context; public RecyclerAdapter(Context context, List articlesList) { this.list = articlesList; this.context = context; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; v= LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_list, parent, false); return new MyViewHolder(v); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { RecyclerEntity entity = list.get(position); if(holder instanceof MyViewHolder){ ((MyViewHolder)holder).title.setText(entity.getTitle()); ((MyViewHolder)holder).imageView.setImageDrawable(context.getResources().getDrawable(entity.getImage())); } } @Override public int getItemCount() { return list.size(); } public class MyViewHolder extends RecyclerView.ViewHolder { TextView title; ImageView imageView; ConstraintLayout container; public MyViewHolder(View itemView) { super(itemView); title = itemView.findViewById(R.id.title); imageView = itemView.findViewById(R.id.imageView); container = itemView.findViewById(R.id.container); } } }

Por lo general, colocamos nuestra subclase ViewHolder (MyViewHolder) en la plantilla de superclase. Esto nos permite devolver directamente nuestro objeto de subclase ViewHolder definido desde el método onCreateViewHolder (). Entonces no tenemos que convertirlo una y otra vez en el método onBindViewHolder ().

Pero aquí no podemos hacer eso, y sabremos por qué en un minuto.

Inicializar RecyclerView en la actividad

public class MainActivity extends AppCompatActivity { RecyclerView recyclerView; List list; RecyclerAdapter adapter; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recyclerview); list = new ArrayList(); list.add(new RecyclerEntity("This is the best title", R.drawable.one, false)); list.add(new RecyclerEntity("This is the second-best title", R.drawable.two, false)); //... rest of the list items adapter = new RecyclerAdapter(this, list); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(adapter); } }

Ahora comencemos a hacer las cosas un poco más interesantes.

Crea un recurso de diseño para el menú

E inicialícelo en Recycler Adapter:

public class RecyclerAdapter extends RecyclerView.Adapter { List list; Context context; private final int SHOW_MENU = 1; private final int HIDE_MENU = 2; public RecyclerAdapter(Context context, List articlesList) { this.list = articlesList; this.context = context; } @Override public int getItemViewType(int position) { if(list.get(position).isShowMenu()){ return SHOW_MENU; }else{ return HIDE_MENU; } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; if(viewType==SHOW_MENU){ v= LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_menu, parent, false); return new MenuViewHolder(v); }else{ v= LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_list, parent, false); return new MyViewHolder(v); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { RecyclerEntity entity = list.get(position); if(holder instanceof MyViewHolder){ //... same as above } if(holder instanceof MenuViewHolder){ //Menu Actions } } @Override public int getItemCount() { return list.size(); } public class MyViewHolder extends RecyclerView.ViewHolder { //... same as above } //Our menu view public class MenuViewHolder extends RecyclerView.ViewHolder{ public MenuViewHolder(View view){ super(view); } } }

Ahora tenemos dos subclases de ViewHolder en nuestro adaptador, MyViewHolder (el elemento de lista real) y MenuViewHolder. Ambos heredan la misma clase, por lo que devolvemos la clase padre RecyclerView.ViewHolder deonCreateViewHolder ().

Nuestro método getItemViewType () devuelve la variable int (viewType) que indica el tipo de vista que queremos mostrar en nuestro RecyclerView para una posición en particular: es decir, MyViewHolder o MenuViewHolder.

Esta variable viewType luego es utilizada por onCreateViewHolder () que en realidad devuelve el objeto ViewHolder respectivo.

Agregue las funciones para mostrar / ocultar el menú en RecyclerAdapter

public void showMenu(int position) { for(int i=0; i

Note that there are many ways to handle this. But for simplicity's sake we're keeping a boolean value in our POJO to maintain the menu's visibility.

After changing our data list, we call the notifyDataSetChanged() method to redraw the list.

Show the menu on long press of our list item in RecyclerAdapter

@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { RecyclerEntity entity = list.get(position); if(holder instanceof MyViewHolder){ ((MyViewHolder)holder).title.setText(entity.getTitle()); ((MyViewHolder)holder).imageView.setImageDrawable(context.getResources().getDrawable(entity.getImage())); ((MyViewHolder)holder).container.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { showMenu(position); return true; } }); } if(holder instanceof MenuViewHolder){ //Set Menu Actions like: //((MenuViewHolder)holder).edit.setOnClickListener(null); } }

Again, setting events on our views can also be done in various ways.

In our example, we have three actions in our menu. You can write your logic to handle those actions in the second if statement like shown in the comments.

Show the menu on swipe

To do this, we add a touch helper in our MainActivity.java:

public class MainActivity extends AppCompatActivity { RecyclerView recyclerView; List list; RecyclerAdapter adapter; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { //... same as above adapter = new RecyclerAdapter(this, list); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(adapter); ItemTouchHelper.SimpleCallback touchHelperCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { private final ColorDrawable background = new ColorDrawable(getResources().getColor(R.color.background)); @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { adapter.showMenu(viewHolder.getAdapterPosition()); } @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); View itemView = viewHolder.itemView; if (dX > 0) { background.setBounds(itemView.getLeft(), itemView.getTop(), itemView.getLeft() + ((int) dX), itemView.getBottom()); } else if (dX < 0) { background.setBounds(itemView.getRight() + ((int) dX), itemView.getTop(), itemView.getRight(), itemView.getBottom()); } else { background.setBounds(0, 0, 0, 0); } background.draw(c); } }; ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchHelperCallback); itemTouchHelper.attachToRecyclerView(recyclerView); }

We call the showMenu() function inside our adapter when a list item is swiped.

The onChildDraw() function draws the background while we swipe. Otherwise there'll be a white background while swiping and our menu layout will show up with a pop.

Hiding the menu

There are three ways to hide our menu.

  1. Hiding the menu when another row is swiped:

This case is already handled in showMenu() method in our Adapter. Before showing the menu for any row, we first call setShowMenu(false) for all the rows to hide the menu.

2.  Hiding the menu when the back button is pressed (in our Activity):

@Override public void onBackPressed() { if (adapter.isMenuShown()) { adapter.closeMenu(); } else { super.onBackPressed(); } }

3.  Hiding the menu when a user scrolls the list:

recyclerView.setOnScrollChangeListener(new View.OnScrollChangeListener() { @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { adapter.closeMenu(); } });

Though pocket only has a long-press action to show the menu, in this example we've added swipe to show the menu for added functionality. You can hide your menu item on swipe right/left again, but I think it might confuse the user.

Wrapping up

If your app has a very large dataset to show in a RecyclerView, this type of UX might not be the way to go. In that case you should have a bulk-edit sort of functionality.

Also if your edit options are more than what you can adjust in a RecyclerView row but you still want to show some quick actions, you can show a Bottomsheet dialog on long press of your item and it can have all your edit options. The Google Drive android app does exactly the same thing.  

If you want to implement a simple swipe to delete function, the code for that can be found here on Github.

You can also check the source code for this project on Github.

Visit 22Boxes.com for more Mobile & Web development resources.

Original text