How to highlight or check selected list items

3.8k views Asked by At

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.

5

There are 5 answers

3
Gary99 On BEST ANSWER

"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>
0
Manohar On

You can show a checkbox in listview with multiselection in following way

add a checkbox in list child xml and set it clickable false in xml

 android:clickable="false"

and for your list OnItemSelectedListner keep this

 ArrayList<Integer> listPositionsSaveArray= new ArrayList<Integer>(); 

  listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                    CheckBox presentCheckBox=(CheckBox)view.findViewById(R.id.checkbox__list);

                    if(presentCheckBox.isChecked()){
                          presentCheckBox.setChecked(false);
                          listPositionsSaveArray.removeAll(Arrays.asList(position+1));
                    }
                    else{
                    presentCheckBox.setChecked(true);
                    listPositionsSaveArray.add(position+1);
                    }


            }
        });

all selected positions of list view will be in listPositionsSaveArray now

2
Aryan Dhankar On

It's very simple. In a layout file use this attributes in your ListView:

android:choiceMode="singleChoice"
android:listSelector="#666666"

If you want to do it programmatically, then try this:

listView.setSelector(Drawable selector)

listView.setSelector(int resourceId)

listview.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);

If you want multiple selection use AbsListView.CHOICE_MODE_MULTIPLE.

0
jagapathi On

As per my understanding you want to change listview row color when user checks that check box..

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

        for (int j = 0; j < adapterView.getChildCount(); j++)
            adapterView.getChildAt(j).setBackgroundColor(Color.TRANSPARENT);

        // change the background color of the selected element
        view.setBackgroundColor(Color.LTGRAY);
});

you can use your logic to implement this changing color as per your requirement

0
Aryan Dhankar On

Okk For multiple selection You need Model,Adapter.So you have to make customlistview.I have given below an examlple.you just have to replace the markAttendance_detail(TextView) to checkbox and if it if 'P' set checkbox check else unchecked.And if you face any problemin this let me know.

Adaper Class-->

public class AttendanceAdapter extends ArrayAdapter {

    List list = new ArrayList();

    static class DataHolder {
        TextView rollNo_detail,studentName_detail,markAttendance_detail;
    }

    public AttendanceAdapter(Context context, int resource) {
        super(context, resource);
    }
    @Override
    public void add(Object object) {
        super.add(object);
        list.add(object);
    }
    @Override
    public int getCount() {
        return this.list.size();
    }

    @Override
    public Object getItem(int position) {
        return this.list.get(position);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row;
        row = convertView;
        final DataHolder dataHolder;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.attendance_list, parent, false);
            dataHolder = new DataHolder();

            dataHolder.rollNo_detail=(TextView)row.findViewById(R.id.student_rollno);
            dataHolder.studentName_detail=(TextView)row.findViewById(R.id.student_name);
            dataHolder.markAttendance_detail=(TextView)row.findViewById(R.id.mark_attendance);
            dataHolder.markAttendance_detail.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    AttendanceModel model=(AttendanceModel) dataHolder.markAttendance_detail.getTag();
                    String att=model.getAttendance().toString();
                    if (model.isSelected()==false){
                        model.setSelected(true);
                        dataHolder.markAttendance_detail.setText("P");
                    }else if (model.isSelected()==true){
                        model.setSelected(false);
                        dataHolder.markAttendance_detail.setText("A");
                    }
                }

            });
            row.setTag(dataHolder);
            dataHolder.markAttendance_detail.setTag(list.get(position));

        }
        else {
            dataHolder = (DataHolder) row.getTag();
            ((DataHolder) row.getTag()).markAttendance_detail.setTag(list.get(position));

        }
        AttendanceModel model = (AttendanceModel) this.getItem(position);

        dataHolder.rollNo_detail.setText(model.getRoll_no());
        dataHolder.studentName_detail.setText(model.getStudent_name());
        if (model.isSelected()==true){
           dataHolder.markAttendance_detail.setText("P");
       }else if (model.isSelected()==false){
            dataHolder.markAttendance_detail.setText("A");
        }
        return row;
    }

Model Should be like this-->

public class AttendanceModel {
    private String roll_no;
    private String student_name;
    private String attendance;
    private boolean selected;
    public AttendanceModel(String roll_no,String student_name,String attendance){
        this.roll_no=roll_no;
        this.selected=true;
        this.student_name=student_name;
        this.attendance=attendance;
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }

    public String getRoll_no() {
        return roll_no;
    }

    public void setRoll_no(String roll_no) {
        this.roll_no = roll_no;
    }

    public String getStudent_name() {
        return student_name;
    }

    public void setStudent_name(String student_name) {
        this.student_name = student_name;
    }

    public String getAttendance() {
        return attendance;
    }

    public void setAttendance(String attendance) {
        this.attendance = attendance;
    }
}

And Activity should be like this-->

public class Attendance_Page extends AppCompatActivity {
    ListView listView;
   AttendanceModel model;
    AttendanceAdapter attendanceAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_atendance_page);
        listView= (ListView) findViewById(R.id.attendancepage_list_view);
    attendanceAdapter=new AttendanceAdapter(getApplicationContext(),R.layout.attendance_list);
       listView.setAdapter(attendanceAdapter);
        for (int i=1;i<=10;i++){
            model=new AttendanceModel("1","Aryan","P");
            attendanceAdapter.add(model);
        }
    }
}

From my point this can be the solution if not let me know .