Search code examples
androidlistviewlong-press

Android Java change ListView to select multiple lines (for deleting) without a separate class


I've searched high and low and I'm beginning to think this isn't possible, but I have a custom ListView which is in an inflated layout and put onto a popup window. The lines are made from a layout file using an adapter. Each item of the list is clickable and takes the user to a different list. I want to also make it so that when I long click an item, it selects that item and then allows me to select multiple lines, and then I have a separate button to delete them. To be clear, I don't want a checkbox on each line (there's no room). Just the lines to get highlighted. I'm stuck at the "selects that item" part, let alone the "select multiple lines". I'm going to truncate my code to the relevant parts. Note: masterRecord is the name of the ListView.

private void fillList() {

        int numRows = mainDB.numRowsMaster();

        if (numRows == 0) {
            Toast.makeText(getApplicationContext(), "Nothing to load!", Toast.LENGTH_SHORT).show();
            return;
        }

        cursor = mainDB.getAllRowsMaster();

        startManagingCursor(cursor);

        String[] fromFieldNames = new String[]{DBAdapter.KEY_MASNAMECOL, DBAdapter.KEY_MASLASTDATECOL, DBAdapter.KEY_MASTOTALTIMEASCLOCKCOL};
        int[] toViewIds = new int[]{R.id.rowName, R.id.rowLastDate, R.id.rowTotalTime};

        SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter(this, R.layout.master_list_row, cursor, fromFieldNames, toViewIds);

        masterRecord.setAdapter(cursorAdapter);
        masterRecord.setLongClickable(true);

    }
private void listItemClick() {
        masterRecord.setOnItemClickListener((parent, viewClicked, position, idInDB) -> {

//Bunch of code here to make the next listview popup and make a cursor adapter. This all works fine.

        });

        masterRecord.setOnItemLongClickListener((parent, view, position, idInDB) -> {

            Toast.makeText(getApplicationContext(), "Is this working?", Toast.LENGTH_SHORT).show();

            masterRecord.setClickable(false); //This doesn't make a difference even though it's running.
//The items in masterRecord are still clickable. Separate issue that I'm not worried about ATM.
            masterRecord.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
            masterRecord.setMultiChoiceModeListener(new ListView.MultiChoiceModeListener() {
                @Override
                public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
                    Toast.makeText(getApplicationContext(), "Here4", Toast.LENGTH_SHORT).show();
                }

                @Override
                public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                    Toast.makeText(getApplicationContext(), "Here1", Toast.LENGTH_SHORT).show();
                    return false;
                }

                @Override
                public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                    Toast.makeText(getApplicationContext(), "Here3", Toast.LENGTH_SHORT).show();
                    return false;
                }

                @Override
                public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                    Toast.makeText(getApplicationContext(), "Here2", Toast.LENGTH_SHORT).show();
                    return false;
                }

                @Override
                public void onDestroyActionMode(ActionMode mode) {
                    Toast.makeText(getApplicationContext(), "Here5", Toast.LENGTH_SHORT).show();
                }
            });

            return true;
        });
    }

The toasts are just so I can see what's running or not on screen. "Is this working?" is the only thing that shows up when I long press. Nothing gets selected, and if I keep long pressing, it just keeps doing the same thing. If I then short press, it just does the normal short press stuff.

I found the Checkable widget and got all excited, but can't seem to make that work either. The tutorials and other information I've seen have a separate class for the ListView, but that seems like overkill. Is that really the only way to do this? If I missed any relevant code, let me know and I'll edit to include. Thank you.


Solution

  • I found the answer in another post (kind of), so I'm going to post what I did here, and link to their answer as well.

    Follow this answer for the setup: https://stackoverflow.com/a/25169360/5374362 That includes making a drawable to use on the row layout. Bear in mind that you can apply the drawable background to any part of your row layout (including the whole layout). It will change whatever you apply it to when it's checked.

    All of this assumes you already have a ListView set up with an adapter.

    Note: masterRecord is the name of my ListView.

    Here is my item long click listener:

    masterRecord.setOnItemLongClickListener((parent, view, position, idInDB) -> {
    
    //On first long click, it sets the choice mode to multiple.
                masterRecord.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    
    //selections is an Integer ArrayList. First entry just sets whatever was long clicked to 
    //checked, and adds the position into the array.
    //I'm recycling layouts (rather than making a bunch), so this also sets an already 
    //existing button to do something.
                if (selections.size() == 0) {
                    masterRecord.setItemChecked(position, true);
                    selections.add(position);
                    switchLists.setText("clear selection");
                    switchLists.setOnClickListener(clearSelection);
                } 
    //In the event someone long clicks on another item, if it's not already selected, it 
    //will get selected. Otherwise it will get unselected.
    //This also shows up in the regular item click listener.
                else
                if (!selections.contains(position)) {
                    masterRecord.setItemChecked(position, true);
                    selections.add(position);
                } else if (selections.contains(position)) {
                    masterRecord.setItemChecked(position, false);
                    selections.remove(Integer.valueOf(position));
    //The next if will set everything back to normal if everything is manually deselected
                    if (selections.size() == 0) {
                        switchLists.setText("View by date");
                        switchLists.setOnClickListener(showDateList);
                        masterRecord.setChoiceMode(ListView.CHOICE_MODE_NONE);
                    }
                }
                return true;
            });
    

    Next is the regular onItemClick()

    masterRecord.setOnItemClickListener((AdapterView<?> parent, View viewClicked, int position, long idInDB) -> {
    
    //This is basically the same code as in the long click listener.
                if (selections.size() > 0) {
    
                    if (!selections.contains(position)) {
                        masterRecord.setItemChecked(position, true);
                        selections.add(position);
                    } else if (selections.contains(position)) {
                        masterRecord.setItemChecked(position, false);
                        selections.remove(Integer.valueOf(position));
                        if (selections.size() == 0) {
                            switchLists.setText("View by date");
                            switchLists.setOnClickListener(showDateList);
                            masterRecord.setChoiceMode(ListView.CHOICE_MODE_NONE);
                        }
                    }
    //return is used here to stop the rest of the onItemClick from running, since it should
    //only run when I'm not trying to multi-select.
                    return;
                }
    

    BONUS - Clear Selection Button:

    View.OnClickListener clearSelection = v -> {
    //A loop which will run through the selections ArrayList and uncheck all the items
    //in the listview, and remove them from the ArrayList.
            for (int i = selections.size()-1; i >= 0; i--) {
                masterRecord.setItemChecked(selections.get(i), false);
                selections.remove(i);
            }
    //Then it just sets everything back to original.
            switchLists.setText("View by date");
            switchLists.setOnClickListener(showDateList);
        };