A „Swipe for Options” megvalósítása a RecyclerView alkalmazásban

Tegyük fel, hogy az Ön webhelyének felhasználói egy listaelemet akarnak szerkeszteni anélkül, hogy megnyitnák az elemet, és szerkesztési lehetőségeket keresnek. Ha engedélyezni tudja ezt a funkciót, jó felhasználói élményt nyújt a felhasználónak .

A Pocket, a Mozilla tulajdonában lévő könyvjelző alkalmazás, hasonlót csinál. A cikk megnyitása nélkül megoszthatja / archiválhatja / törölheti mentett cikkeit közvetlenül a listáról. Ezután kattintson a jobb felső sarokban található menü gombra, és válassza ki a szerkesztési lehetőséget.

Tehát ebben az oktatóanyagban megpróbáljuk kódolni ezt.

A következőket akarjuk elérni :

Először hozzunk létre egy normál RecyclerView listát

A RecyclerView a ListView és a GridView fejlett és rugalmas változata. Nagy mennyiségű listás adat tárolására képes, és jobb teljesítményt nyújt, mint elődei.

Ahogy a neve is sugallja, a RecyclerView „újrahasznosítja” a listánk elemeit, ha már nincs kilátás a görgetésre, és újra feltölti őket, amikor visszatérnek megtekinteni. Tehát a listatárolónak csak korlátozott számú nézetet kell fenntartania, és nem a teljes listát.

Olyan rugalmas, hogy az új ViewPager2 osztály, amelyet ellopásra képes fülek létrehozására használnak, a RecyclerView-ra íródik.

Hozzon létre egy POJO-t (Plain Old Java Object) a lista adatainak tárolásához

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 }

Figyelje meg, hogy itt van egy showMenu tagunk, aki kezeli a menü láthatóságát az adott listaelem számára a RecyclerView-ban.

Hozzon létre egy RecyclerView adaptert

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); } } }

Általában a ViewHolder alosztályunkat (MyViewHolder) a super class sablonba helyezzük. Ez lehetővé teszi, hogy közvetlenül megadjuk a definiált ViewHolder alosztály objektumunkat az onCreateViewHolder () metódusból. Akkor nem kell újra és újra leadnunk az onBindViewHolder () metódusba.

De itt ezt nem tudjuk megtenni, és egy perc alatt megtudjuk, miért.

Inicializálja a RecyclerView elemet a tevékenységben

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); } }

Kezdjük egy kicsit érdekesebbé tenni a dolgokat.

Hozzon létre egy elrendezési erőforrást a menühöz

És inicializálja az Újrahasznosító adapterben:

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); } } }

Most két ViewHolder alosztály van az adapterünkben, a MyViewHolder (a tényleges listaelem) és a MenuViewHolder. Mindkét örökli az azonos osztályba, így visszatérünk a szülő osztály RecyclerView.ViewHolder származóonCreateViewHolder ().

A getItemViewType () metódusunk az int változót (viewType) adja vissza, amely megmondja, hogy milyen nézetet szeretnénk megjeleníteni a RecyclerView-nkban egy adott pozíció esetében: vagyis a MyViewHolder vagy a MenuViewHolder.

Ezt a viewType változót azután az onCreateViewHolder () használja, amely valójában visszaadja a megfelelő ViewHolder objektumot.

Adja hozzá a függvényeket a RecyclerAdapter menü megjelenítéséhez / elrejtéséhez

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