Search code examples
androidlistviewgridviewcontextual-action-barmultichoiceitems

How to highlight or check selected list items


How should I go about implementing Java or XML code for highlighting (or showing a checkbox over) a list row or grid item in Multiple-Choice Modal mode in Android for use with a contextual action bar?

I have implemented the contextual action bar and the ListView/GridView and I can select them and run functions on the selected items but there is no visual feedback except for the brief highlighting of the list row/item when you long-click on it, which disappears when it is released.

My first thought was to set the background color of the row/item in the adapter but I cannot seem to get it to work. I have also tried the solution suggested by the accepted answer of this question: Android ListView Multi-Choice don't show highlight after chlicking but it didn't have any effect on the behavior of my ListView and GridView.

I am mostly interested in knowing the standard way of doing this according to material design guidelines and/or the most common way. Thank you in advance for any advice or solution.

EDIT

I tried Redman's answer (in fact something similar to it, as I am using the Contextual Action Mode and a multiple-choice listener) but I didn't get any result. Here's what I did in the listener:

public void onItemCheckedStateChanged(ActionMode actionMode, int i, long id, boolean checked) {
            if (checked) {
                selectedItems.add(listAdapter.getItem(i));
                ((CheckBox) listAdapter.getView(i,null,listView).findViewById(R.id.listCheckBox)).setChecked(true);
            }
            else {
                selectedItems.remove(listAdapter.getItem(i));
                ((CheckBox) listAdapter.getView(i,null,listView).findViewById(R.id.listCheckBox)).setChecked(false);
            }
        }

It runs without an error but it doesn't do anything to the checkbox so I'm not sure what the problem is. Any help is really appreciated.


Solution

  • "I am mostly interested in knowing the standard way of doing this according to material design guidelines and/or the most common way."

    I'm not sure my answer is material design or common but I've done a GridView based off handling of selections in the Google "Photos" app. I have tested it and know that it will both include a highlighted border and a 'checkbox'.

    ** disclaimers **

    I considered using a selector but there seems to be problems with that if your view scrolls (see this). Also, I used a custom checkbox (really just two different drawables), partially because of this. Also, this answer just basically handles a phone in portrait mode. More would need to be added to handle different configurations (screen size, orientation, etc.)

    There's a lot here, but like I said, I've tested it. The whole project has been posted to GitHub.

    1st, in MainActivity onCreate()

    mList = new ArrayList<>();
    
    // fill your list here
    
    mAdapter = new GridAdapter(getApplicationContext(), R.layout.grid_item, mList);
    GridView gridView = (GridView)findViewById(R.id.grid_view);
    gridView.setAdapter(mAdapter);
    gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
            Log.d(TAG, "item clicked = " + i);
            boolean isSelected = mList.get(i).isSelected();
            mList.get(i).setSelected(!isSelected);
    
            mAdapter.notifyDataSetChanged();
        }
    });
    

    Next, overide getView() in your Adapter (I'm using an ArrayAdapter)

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
    
        RelativeLayout itemLayout;
    
        GridItem gridItem = (GridItem)getItem(position);
    
        // use existing Views when we can
        if(convertView == null) {
            LayoutInflater inflater = (LayoutInflater)
                    getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            itemLayout = (RelativeLayout) inflater.inflate(R.layout.grid_item, null);
        } else {
            itemLayout = (RelativeLayout) convertView;
        }
    
        // get your bitmap or list item etc. here
        BitmapDrawable bitmap = gridItem.getBitmap();
        ImageView imageView = (ImageView)itemLayout.findViewById(R.id.image_view);
        imageView.setBackground(bitmap);
    
        if(gridItem.isSelected()) {
            final int PADDING = 16;
    
            Log.d(TAG, "position " + position + " is selected");
            itemLayout.findViewById(R.id.image_frame).setPadding(PADDING, PADDING, PADDING, PADDING);
            itemLayout.findViewById(R.id.custom_check_box)
                    .setBackgroundResource(R.drawable.custom_check_box_selected);
        } else {
            Log.d(TAG, "postion " + position + " is NOT selected");
            itemLayout.findViewById(R.id.image_frame).setPadding(0,0,0,0);
            itemLayout.findViewById(R.id.custom_check_box)
                    .setBackgroundResource(R.drawable.custom_check_box_unselected);
        }
        return itemLayout;
    }
    

    Here's the core of the GridItem class, left out the getters & setters.

    public class GridItem {
    
        private BitmapDrawable bitmap = null;
        private boolean isSelected = false;
    
        public GridItem(BitmapDrawable bitmap) {
            this.bitmap = bitmap;
        }
    }
    

    That's it for the Java. Now for some xml

    grid_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        >
        <FrameLayout
            android:layout_width="140dp"
            android:layout_height="140dp"
            android:id="@+id/image_frame"
            >
            <!-- view for the main image -->
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="center"
                android:background="@android:color/white"
                android:id="@+id/image_view"
                />
        </FrameLayout>
    
        <!-- view for the 'checkbox' in upper left corner -->
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:src="@drawable/custom_check_box_unselected"
            android:id="@+id/custom_check_box"
            />
    </RelativeLayout>
    

    And this would go in content_main.xml or activity_main.xml, etc.

    <GridView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:numColumns="2"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="20dp"
        android:stretchMode="columnWidth"
        android:gravity="center"
        android:id="@+id/grid_view"
        >
    </GridView>
    

    And now 2 files for your drawable folder.

    custom_check_box_unselected.xml

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="20dp"
        android:width="20dp"
    
        android:viewportWidth="400"
        android:viewportHeight="400">
    
        <!-- the outside box -->
        <!-- top line & top left corner -->
        <path android:pathData="M 340 30 H 62 c -20 0 -35 15 -35 35 "
            android:strokeColor="#000000" android:strokeWidth="20" />
    
        <!-- left line & bottom left corner -->
        <path android:pathData="M 27 64 v271 c0 20 15 35 35 35 "
            android:strokeColor="#000000" android:strokeWidth="20" />
    
        <!-- bottom line & bottom right corner -->
        <path android:pathData="M 60 370 h275 c20 0 35 -15 35 -35"
            android:strokeColor="#000000" android:strokeWidth="20" />
    
        <!-- right line & top right corner -->
        <path android:pathData="M 370 336 v -271 c0 -20 -15 -35 -35  -35"
            android:strokeColor="#000000" android:strokeWidth="20" />
    </vector>
    

    custom_check_box_selected.xml

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="20dp"
        android:width="20dp"
    
        android:viewportWidth="400"
        android:viewportHeight="400">
    
        <!-- the outside box -->
        <path android:pathData="M 340 30 H 62 c -20 0 -35 15 -35 35
                v271 c0 20 15 35 35 35
                h275 c20 0 35 -15 35 -35
                v -271 c0 -20 -15 -35 -35  -35 "
            android:fillColor="#FDD835"  android:strokeColor="#000000" android:strokeWidth="20" />
    
        <!-- the check mark -->
        <path android:pathData="M 140 320 l -100 -100 25 -30
                l 75 75 l 190 -190
                l 25 30 l -190 190"
            android:fillColor="#000000"  android:strokeColor="#000000" android:strokeWidth="2" />
    </vector>