Sådan implementeres 'Stryg efter muligheder' i RecyclerView

Lad os sige, at en bruger af dit websted vil redigere et listeelement uden at åbne elementet og se efter redigeringsmuligheder. Hvis du kan aktivere denne funktionalitet, giver den brugeren en god brugeroplevelse .

Pocket, en bogmærke-app ejet af Mozilla, gør noget lignende. Du kan dele / arkivere / slette dine gemte artikler direkte fra listen uden at åbne artiklen. Derefter kan du klikke på menuknappen i øverste højre hjørne og vælge din redigeringsindstilling.

Så i denne vejledning prøver vi at kode denne ud.

Her er hvad vi vil opnå :

Lad os først oprette en normal RecyclerView-liste

RecyclerView er en avanceret og fleksibel version af ListView og GridView. Det er i stand til at holde store mængder af listedata og har bedre ydeevne end sine forgængere.

Som navnet antyder, 'genbruger' RecyclerView emnerne på vores liste, når den er ude af syne ved at rulle og genudfylder dem, når de kommer tilbage for at se dem. Så beholderen til listen behøver kun at have et begrænset antal visninger og ikke hele listen.

Det er så fleksibelt, at den nye ViewPager2-klasse, der bruges til at oprette faner, der er i stand til at skubbe, er skrevet over RecyclerView.

Opret et POJO (almindeligt gammelt Java-objekt) for at holde listedataene

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 }

Bemærk, at vi har et showMenu-medlem her, som håndterer synligheden af ​​menuen for det listeelement i vores RecyclerView.

Opret en RecyclerView-adapter

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

Normalt lægger vi vores ViewHolder-underklasse (MyViewHolder) i superklasseskabelonen. Dette giver os mulighed for direkte at returnere vores definerede ViewHolder-underklasseobjekt fra onCreateViewHolder () -metoden. Så behøver vi ikke at kaste det igen og igen i onBindViewHolder () -metoden.

Men her kan vi ikke gøre det, og vi lærer hvorfor om et øjeblik.

Initialiser RecyclerView i aktiviteten

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

Lad os nu begynde at gøre tingene lidt mere interessante.

Opret en layoutressource til menuen

Og initialiser det i 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); } } }

Nu har vi to ViewHolder-underklasser i vores adapter, MyViewHolder (det aktuelle listeelement) og MenuViewHolder. Begge arver den samme klasse, så vi returnerer moderklassen RecyclerView.ViewHolder fraonCreateViewHolder ().

Vores getItemViewType () -metode returnerer int-variablen (viewType), der fortæller den slags visning, vi vil vise i vores RecyclerView for en bestemt position: det vil sige enten MyViewHolder eller MenuViewHolder.

Denne viewType-variabel bruges derefter af onCreateViewHolder (), som faktisk returnerer det respektive ViewHolder-objekt.

Tilføj funktionerne for at vise / skjule menuen i 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.