Πώς να εφαρμόσετε το «Swipe for Options» στο RecyclerView

Ας υποθέσουμε ότι ένας χρήστης του ιστότοπού σας θέλει να επεξεργαστεί ένα στοιχείο λίστας χωρίς να ανοίξει το στοιχείο και να αναζητήσει επιλογές επεξεργασίας. Εάν μπορείτε να ενεργοποιήσετε αυτήν τη λειτουργικότητα, δίνει στον χρήστη μια καλή εμπειρία χρήστη .

Το Pocket, μια εφαρμογή σελιδοδεικτών που ανήκει στη Mozilla, κάνει κάτι παρόμοιο. Μπορείτε να μοιραστείτε / αρχειοθετήσετε / διαγράψετε τα αποθηκευμένα άρθρα σας απευθείας από τη λίστα χωρίς να ανοίξετε το άρθρο. Στη συνέχεια, μπορείτε να κάνετε κλικ στο κουμπί μενού στην επάνω δεξιά γωνία και να επιλέξετε την επιλογή επεξεργασίας.

Έτσι σε αυτό το σεμινάριο θα προσπαθήσουμε να το κωδικοποιήσουμε.

Να τι θέλουμε να πετύχουμε :

Αρχικά, ας δημιουργήσουμε μια κανονική λίστα RecyclerView

Το RecyclerView είναι μια προηγμένη και ευέλικτη έκδοση του ListView και του GridView. Είναι ικανό να διατηρεί μεγάλες ποσότητες δεδομένων λίστας και έχει καλύτερη απόδοση από τους προκατόχους του.

Όπως υποδηλώνει το όνομα, το RecyclerView «ανακυκλώνει» τα στοιχεία της λίστας μας όταν δεν είναι διαθέσιμο κατά την κύλιση και τα συμπληρώνει ξανά όταν επιστρέψουν στην προβολή. Επομένως, το κοντέινερ λίστας πρέπει να διατηρεί μόνο έναν περιορισμένο αριθμό προβολών και όχι ολόκληρη τη λίστα.

Είναι τόσο ευέλικτο που η νέα τάξη ViewPager2, που χρησιμοποιείται για τη δημιουργία καρτελών με δυνατότητα σάρωσης, γράφεται πάνω από το RecyclerView.

Δημιουργήστε ένα POJO (Plain Old Java Object) για να κρατήσετε τα δεδομένα της λίστας

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 }

Παρατηρήστε ότι έχουμε ένα μέλος του showMenu που θα χειρίζεται την ορατότητα του μενού για αυτό το στοιχείο λίστας στο RecyclerView.

Δημιουργήστε έναν προσαρμογέα 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); } } }

Συνήθως βάζουμε την υποκατηγορία ViewHolder (MyViewHolder) στο πρότυπο super class. Αυτό μας επιτρέπει να επιστρέψουμε απευθείας το καθορισμένο αντικείμενο υποκατηγορίας ViewHolder από τη μέθοδο onCreateViewHolder (). Τότε δεν χρειάζεται να το ρίξουμε ξανά και ξανά στη μέθοδο onBindViewHolder ().

Αλλά εδώ δεν μπορούμε να το κάνουμε αυτό και θα μάθουμε γιατί σε ένα λεπτό.

Αρχικοποιήστε το RecyclerView στη Δραστηριότητα

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

Τώρα ας αρχίσουμε να κάνουμε τα πράγματα λίγο πιο ενδιαφέροντα.

Δημιουργήστε έναν πόρο διάταξης για το μενού

Και αρχικοποιήστε το στο 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); } } }

Τώρα έχουμε δύο υποκατηγορίες ViewHolder στον προσαρμογέα μας, το MyViewHolder (το πραγματικό στοιχείο λίστας) και το MenuViewHolder. Και οι δύο κληρονομούν την ίδια τάξη, γι 'αυτό επιστρέφουμε τη γονική κλάση RecyclerView.ViewHolder απόonCreateViewHolder ().

Η μέθοδος getItemViewType () επιστρέφει τη μεταβλητή int (viewType) που αναφέρει το είδος της προβολής που θέλουμε να δείξουμε στο RecyclerView για μια συγκεκριμένη θέση: δηλαδή, είτε το MyViewHolder είτε το MenuViewHolder.

Στη συνέχεια, αυτή η μεταβλητή viewType χρησιμοποιείται από το onCreateViewHolder () που επιστρέφει στην πραγματικότητα το αντίστοιχο αντικείμενο ViewHolder.

Προσθέστε τις λειτουργίες για εμφάνιση / απόκρυψη μενού στο 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.