I am trying to build a recyclerview with the same functionality like f.ex. wunderlist. LongClick should activate action mode with the long-clicked item selected, and further items added to the selection by normal clicking them in action mode.
BUT I also want to be able to rearrange the items with ItemTouchHelper. So my imagined process looks like this:
Long-click on item: Add long-clicked item to selection & enter action mode.
If item released without moving: stay in action mode and add further normal-clicked items to selection.
BUT if item is moved (ItemTouchHelper): Finish ActionMode & clear selection, rearrange list accordingly, notifyAdapterItemMoved().
What I have seems to work when I move the item IMMEDIATELY after the long click is registered. But a split second longer and it stops dragging the item and leaves me in action mode.
How can I keep the multi-selection functionality until the selected item is dragged over (moved past) one of it's nearest neighbours?
Thanks for any help!
PersonListFragment: The Fragment containing the list
public class PersonListFragment extends Fragment implements
PersonAdapter.OnItemClickListener, androidx.appcompat.view.ActionMode.Callback {
// variables omitted from brevity
public PersonListFragment() { }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_person_list, container, false);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = getActivity().findViewById(R.id.recycler_view_people);
buttonAddPerson = getActivity().findViewById(R.id.button_add_person);
navController = Navigation.findNavController(getActivity(), R.id.nav_host);
adapter = new PersonAdapter(this);
viewModel = new ViewModelProvider(this).get(PersonViewModel.class);
handleButtons();
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Should be in activity created. Otherwise list is recreated every time this fragment opens,
// which causes visual lag.
viewModel.getAllPeopleWithPets().observe(getActivity(), new Observer<List<PersonWithPets>>() {
@Override
public void onChanged(@Nullable List<PersonWithPets> allPeopleWithPets) {
people = allPeopleWithPets;
adapter.setPeople(people);
}
});
initRecyclerView();
new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
multiSelect = false;
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
multiSelect = false;
if (actionMode != null) {
actionMode.finish();
}
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
PersonWithPets fromPerson = people.get(fromPosition);
people.remove(fromPerson);
people.add(toPosition, fromPerson);
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
Toast.makeText(getActivity(), "Swiped", Toast.LENGTH_SHORT).show();
}
}).attachToRecyclerView(recyclerView);
}
private void initRecyclerView() {
recyclerView.setLayoutManager(new LinearLayoutManager(this.getActivity()));
recyclerView.setHasFixedSize(true);
adapter.setOnItemClickListener(this);
recyclerView.setAdapter(adapter);
}
// CLICK
@Override
public void onItemClick(int position) {
if (!multiSelect) {
Person clickedPerson = people.get(position).getPerson();
Bundle bundle = new Bundle();
bundle.putInt("id", clickedPerson.getId());
bundle.putString("name", clickedPerson.getName());
bundle.putString("time", clickedPerson.getTime());
bundle.putString("howto", clickedPerson.getHowto());
navController.navigate(R.id.action_homeTabs_to_personTabs, bundle);
Toast.makeText(getActivity(), "Clicked.", Toast.LENGTH_SHORT).show();
} else {
multiSelect(position);
}
}
// LONG CLICK
@Override
public void onItemLongClick(int position) {
if (!multiSelect) {
multiSelect = true;
selected = new ArrayList<>();
}
if (actionMode == null) {
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(PersonListFragment.this);
}
multiSelect(position);
}
// FAV CLICK
@Override
public void onFavClick(int position) {
if (!multiSelect) {
Person person = people.get(position).getPerson();
if (!person.getSelected()) {
person.setSelected(true);
} else {
person.setSelected(false);
}
viewModel.update(person);
adapter.notifyItemChanged(position);
}
}
// MULTI SELECT
private void multiSelect(int position) {
Person person = adapter.getItem(position).getPerson();
if (person != null) {
if (selected.contains(person.getId())) {
selected.remove(person.getId());
} else {
selected.add(person.getId());
}
if (selected.size() > 0) {
actionMode.setTitle(selected.size() + " items selected.");
} else {
multiSelect = false;
selected = new ArrayList<>();
actionMode.setTitle("");
actionMode.finish();
}
adapter.setSelected(selected);
}
}
// BUTTONS
private void handleButtons() {
buttonAddPerson.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navController.navigate(R.id.personTabs);
Toast.makeText(getActivity(), "That tickles.", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public boolean onCreateActionMode(androidx.appcompat.view.ActionMode mode, Menu menu) {
Log.d(TAG, "onCreateActionMode: CREATED ACTION MODE");
actionMode = mode;
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.action_menu, menu);
return true;
}
@Override
public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(androidx.appcompat.view.ActionMode mode, MenuItem item) {
Log.d(TAG, "onActionItemClicked: ACTION ITEM CLICKED");
switch (item.getItemId()) {
case R.id.button_delete:
viewModel.deletePeopleById(selected);
Toast.makeText(getActivity(), selected.size() + " people deleted.", Toast.LENGTH_SHORT).show();
mode.finish();
return true;
case R.id.button_favorite:
for (PersonWithPets personWithPets : people) {
Person person = personWithPets.getPerson();
if (selected.contains(person.getId())) {
person.setSelected(true);
viewModel.update(person);
Log.d(TAG, "onActionItemClicked: FAVORITE " + person.getName() + " updated.");
}
}
Toast.makeText(getActivity(), "<3", Toast.LENGTH_SHORT).show();
mode.finish();
return true;
}
return false;
}
@Override
public void onDestroyActionMode(androidx.appcompat.view.ActionMode mode) {
multiSelect = false;
selected.clear();
adapter.notifyDataSetChanged();
actionMode = null;
Log.d(TAG, "onDestroyActionMode: ACTION MODE DESTROYED");
}
}
PersonAdapter: The recyclerview adapter
public class PersonAdapter extends RecyclerView.Adapter<PersonAdapter.PersonHolder> {
private static final String TAG = "PersonAdapter";
private List<PersonWithPets> people = new ArrayList<>();
private List<Integer> selected = new ArrayList<>();
private OnItemClickListener listener;
private View clickedView;
private boolean multiSelect;
Context context;
private ItemTouchHelper itemTouchHelper;
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public interface OnItemClickListener {
void onItemClick(int position);
void onItemLongClick(int position);
void onFavClick(int position);
}
public PersonAdapter(OnItemClickListener onItemClickListener) {
this.listener = onItemClickListener;
}
@NonNull
@Override
public PersonHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.person_item, parent, false);
return new PersonHolder(itemView, listener);
}
@Override
public void onBindViewHolder(@NonNull PersonHolder holder, int position) {
PersonWithPets currentPerson = people.get(position);
int id = currentPerson.getPerson().getId();
boolean fav = currentPerson.getPerson().getSelected();
holder.textViewName.setText( currentPerson.getPerson().getName());
holder.textViewHowTo.setText( currentPerson.getPerson().getHowto());
holder.textViewTime.setText( currentPerson.getPerson().getTime());
holder.textViewPetAmount.setText( currentPerson.getPets().size() + " Pets");
// Multi select
if (selected.contains(id)){
holder.rootView.setBackgroundColor(Color.CYAN);
} else {
holder.rootView.setBackgroundColor(Color.WHITE);
}
// Favorited?
if (fav) {
holder.imageViewFav.setBackgroundColor(Color.CYAN);
holder.imageViewFav.animate();
} else {
holder.imageViewFav.setBackgroundColor(Color.TRANSPARENT);
holder.imageViewFav.animate();
}
}
@Override
public int getItemCount() {
return people.size();
}
// VIEWHOLDER
public class PersonHolder extends RecyclerView.ViewHolder
{
private CardView rootView;
private TextView textViewName, textViewHowTo, textViewPetAmount, textViewTime;
private ImageView imageViewFav, imageViewPerson;
OnItemClickListener listener;
public PersonHolder(View itemView, OnItemClickListener onItemClickListener){
super(itemView);
rootView = itemView.findViewById(R.id.person_item);
textViewName = itemView.findViewById(R.id.text_view_person_name);
textViewPetAmount = itemView.findViewById(R.id.text_view_pet_amount);
textViewTime = itemView.findViewById(R.id.text_view_prep_time);
imageViewFav = itemView.findViewById(R.id.image_view_fav);
imageViewPerson = itemView.findViewById(R.id.image_view_person);
textViewHowTo = itemView.findViewById(R.id.text_view_person_description);
listener = onItemClickListener;
rootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position);
}
}
}
});
rootView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onItemLongClick(position);
}
}
return true;
}
});
imageViewFav.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onFavClick(position);
}
}
}
});
}
}
// Custom Methods
public PersonWithPets getItem(int position) {
return people.get(position);
}
public void setSelected(List<Integer> selected) {
this.selected = selected;
notifyDataSetChanged();
}
public void setPeople(List<PersonWithPets> people) {
this.people = people;
notifyDataSetChanged();
}
}