Cách triển khai 'Swipe for Options' trong RecyclerView

Giả sử một người dùng trang web của bạn muốn chỉnh sửa một mục danh sách mà không cần mở mục đó và tìm kiếm các tùy chọn chỉnh sửa. Nếu bạn có thể bật chức năng này, nó sẽ mang lại cho người dùng đó Trải nghiệm người dùng tốt .

Pocket, một ứng dụng đánh dấu trang do Mozilla sở hữu, cũng làm điều tương tự. Bạn có thể chia sẻ / lưu trữ / xóa các bài viết đã lưu của mình trực tiếp khỏi danh sách mà không cần mở bài viết. Sau đó, bạn có thể nhấp vào nút menu ở góc trên bên phải và chọn tùy chọn chỉnh sửa của mình.

Vì vậy, trong hướng dẫn này, chúng tôi sẽ cố gắng viết mã này.

Đây là những gì chúng tôi muốn đạt được :

Đầu tiên, hãy tạo một danh sách RecyclerView bình thường

RecyclerView là phiên bản nâng cao và linh hoạt của ListView và GridView. Nó có khả năng chứa một lượng lớn dữ liệu danh sách và có hiệu suất tốt hơn so với các phiên bản tiền nhiệm.

Như tên cho thấy, RecyclerView 'tái chế' các mục trong danh sách của chúng tôi khi nó không thể xem được khi cuộn và điền lại chúng khi họ quay lại xem. Vì vậy, vùng chứa danh sách chỉ phải duy trì một số lượt xem giới hạn chứ không phải toàn bộ danh sách.

Nó linh hoạt đến mức lớp ViewPager2 mới, được sử dụng để tạo các tab có thể vuốt, được ghi trên RecyclerView.

Tạo một POJO (Đối tượng Java cũ thuần túy) để giữ dữ liệu danh sách

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 }

Lưu ý rằng chúng tôi có một thành viên showMenu ở đây sẽ xử lý khả năng hiển thị của menu cho mục danh sách đó trong RecyclerView của chúng tôi.

Tạo bộ điều hợp 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); } } }

Thông thường, chúng tôi đặt lớp con ViewHolder (MyViewHolder) của chúng tôi trong mẫu siêu lớp. Điều này cho phép chúng tôi trực tiếp trả về đối tượng lớp con ViewHolder đã xác định của chúng tôi từ phương thức onCreateViewHolder (). Sau đó, chúng ta không cần phải truyền nó nhiều lần trong phương thức onBindViewHolder ().

Nhưng ở đây chúng tôi không thể làm điều đó, và chúng tôi sẽ tìm hiểu lý do tại sao trong một phút.

Khởi tạo RecyclerView trong Activity

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

Bây giờ hãy bắt đầu làm cho mọi thứ thú vị hơn một chút.

Tạo tài nguyên bố cục cho menu

Và khởi tạo nó trong 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); } } }

Bây giờ chúng ta có hai lớp con ViewHolder trong bộ điều hợp của chúng ta, MyViewHolder (mục danh sách thực tế) và MenuViewHolder. Cả hai đều kế thừa cùng một lớp nên chúng tôi trả về lớp cha RecyclerView.ViewHolder từonCreateViewHolder ().

Phương thức getItemViewType () của chúng tôi trả về biến int (viewType) cho biết loại chế độ xem chúng tôi muốn hiển thị trong RecyclerView cho một vị trí cụ thể: đó là MyViewHolder hoặc MenuViewHolder.

Biến viewType này sau đó được sử dụng bởi onCreateViewHolder () thực sự trả về đối tượng ViewHolder tương ứng.

Thêm các chức năng để hiển thị / ẩn menu trong 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.