4

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:

  1. Long-click on item: Add long-clicked item to selection & enter action mode.

  2. If item released without moving: stay in action mode and add further normal-clicked items to selection.

  3. 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();
        }
    }

0 Answers0