Android gridview keep item selected

I have a GridView with multiple items, but the items must be kept selected once the the onClickListener is called.How can i achive this?

I'v already tried v.setSelected(true) but it doesnt seem to work.

gridview.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View v,
                int position, long id) {
            // Toast.makeText(Project.this, "Red" + position,
            // Toast.LENGTH_SHORT).show(); //position = al catelea element
            v.setPressed(true);
            if (bp == 2) {
                if (position == 0) {
                Square.setSex(R.drawable.girl_body2v);
                Square2.setHair(R.drawable.girl_hair_01v);
                SquareAccesories.setAcc(R.drawable.girl_accessories_01v);
                SquareEyes.setEyes(R.drawable.eyes_1v);
                SquareLips.setLips(R.drawable.lip_1v);
                Square3.setDress(R.drawable.girl_tops_01v);
                SquareShoes.setShoes(R.drawable.girl_shoes_01v);
                SquarePants.setPants(R.drawable.girl_bottom_01v);
                setS(2);

This is a small part of the code for the onClickListener because i have lots of cases.


Solution 1:

I think a better approach is to tell the GridView that you wish to support selecting (checking) the items:

gridView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE);

and then make sure that items in GridView implement Checkable interface. That means that the items can be either Checkbox, ToggleButton and so on or you can add the Checkable support yourself - for example make RelativeLayout checkable. (See the example below.)

In contrast to the other answer most of the work is taken care of by the GridView itself - no onClickListener is needed. Instead of storing the state yourself, just call gridView.getCheckedItemIds() or similar method.


To make RelativeLayout (or anything) checkable make a subclass of it:

public class CheckableRelativeLayout extends RelativeLayout implements Checkable {
    private boolean checked = false;
    private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };

    public CheckableRelativeLayout(Context context) {
        super(context);
    }

    public CheckableRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
         if (isChecked())
             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
         return drawableState;
    }

    @Override
    public boolean isChecked() {
        return checked;
    }

    @Override
    public void setChecked(boolean _checked) {
        checked = _checked;
        refreshDrawableState();
    }

    @Override
    public void toggle() {
        setChecked(!checked);
    }

}

Notice that the method onCreateDrawableState updates the visual style. You don't have to do it this way, you can for example directly change the background in the setChange method.

Then use the CheckableRelativeLayout as the top view for items in the GridView:

<foo.bar.CheckableRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@drawable/my_awesome_background"
    ... more stuff
    >
        ...  content of the relative layout
</com.test.CheckableRelativeLayout>

And define how the background changes when the item is checked in res/drawable/my_awesome_background.xml:

<selector xmlns:android="http://schemas.android.com/apk/res/android" > 
     <item android:state_checked="true" >
        <!-- This applies when the item is checked. -->
         <shape android:shape="rectangle"  >
             <solid android:color="#A8DFF4" />
         </shape>
     </item>

    <item>
        <!-- This applies when the item is not checked. -->
        <shape android:shape="rectangle"  >
             <solid android:color="#EFEFEF" />
         </shape>
     </item>
</selector>

Solution 2:

The concept that you want to achieve is possible, but not like the way you are working now.

The best and easiest solution would be to keep track of the states of the clicked items and give them the correct layout inside the adapter. I have set up a little example:

Activity

public class StackOverFlowActivity extends Activity {
    GridView gridView;
    MyCustomAdapter myAdapter;
    ArrayList<GridObject> myObjects;

    static final String[] numbers = new String[] { "A", "B", "C", "D", "E",
            "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z" };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        myObjects = new ArrayList<GridObject>();
        for (String s : numbers) {
            myObjects.add(new GridObject(s, 0));
        }

        gridView = (GridView) findViewById(R.id.gridView1);

        myAdapter = new MyCustomAdapter(this);

        gridView.setAdapter(myAdapter);
        gridView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> arg0, View v, int position, long arg3) {
                myObjects.get(position).setState(1);
                myAdapter.notifyDataSetChanged();
            }
        });
    }

    static class ViewHolder {
        TextView text;
    }

    private class MyCustomAdapter extends BaseAdapter  {

        private LayoutInflater mInflater;

        public MyCustomAdapter(Context context) {
            mInflater = LayoutInflater.from(context);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            GridObject object = myObjects.get(position);
            ViewHolder holder;

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            holder.text.setText(object.getName());

            if (object.getState() == 1) {
                holder.text.setBackgroundColor(Color.GREEN);
            } else {
                holder.text.setBackgroundColor(Color.BLUE);
            }
            return convertView;
        }

        @Override
        public int getCount() {
            return myObjects.size();
        }

        @Override
        public Object getItem(int position) {
            return position;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }
    }
}

GridObject

public class GridObject {

    private String name;
    private int state;

    public GridObject(String name, int state) {
        super();
        this.name = name;
        this.state = state;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }   
}

Main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <GridView
        android:id="@+id/gridView1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:columnWidth="50dp"
        android:gravity="center"
        android:numColumns="auto_fit"
        android:stretchMode="columnWidth" >
    </GridView>

</LinearLayout>

list_item_icon_text

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

Solution 3:

Here's a terser version of Strix's answer (which I think is better than the accepted answer) when you don't need to reuse that code elsewhere. Instead of creating a new class and implementing Checked, you can just create an anonymous class inside your Adapter.getView method, overriding onCreateDrawableState as in Strix's answer, but replace isChecked() there with ((AbsListView)parent).isItemChecked(position). Here's the full code from my adapter that draws a border around checked thumbnails in a gallery:

public class ImageAdapter extends BaseAdapter {
    private final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
    public int getCount() {return images.size();}
    public Object getItem(int position) {return images.get(position);}
    public long getItemId(int position) {return position;}

    public View getView(final int position, final View convertView, final ViewGroup parent) {
        final ImageView imageView = new ImageView(getApplicationContext()) {
            @Override public int[] onCreateDrawableState(int extraSpace) {
                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
                if (((AbsListView)parent).isItemChecked(position)) {
                    mergeDrawableStates(drawableState, CHECKED_STATE_SET);
                }
                return drawableState;
            }
        };
        imageView.setBackground(getResources().getDrawable(R.drawable.my_awesome_background));
        imageView.setScaleType(ImageView.ScaleType.CENTER);
        final byte[] buffer = images.get(position);
        final Bitmap bmp = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
        imageView.setImageBitmap(bmp);
        return imageView;
    }
}

Solution 4:

I know the answer is a little bit old. But i think it's the easiest one of all. In your Adapter Class, add a variable containing the selected Item Position. Set transperency to all Images excluding the selected one in GetView method. In your click handler in main method, safe the selected ItemPosition. Notify the adapter that he has changed.

In your Adapter Class, add a variable containing the selected Item Position.

public class GridImageAdapter extends BaseAdapter {
       public int selectedImage = 0;

Set transperency to all Images excluding the selected one in Adapter GetView method.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
    int[] images = { R.drawable.walk, R.drawable.run, R.drawable.jump }

    ImageView imageView = new ImageView(mContext);
    if (position < imgMapper.length) {

        imageView.setImageResource(images[position]);

        if (position != selectedImage) {
            imageView.setImageAlpha(50);
        }
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setLayoutParams(new GridView.LayoutParams(150, 150));
    };

    return imageView;
}

In your click handler in main method, safe the selected ItemPosition. Notify the adapter that he has changed

   myGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
            GridImageAdapter myAdapter = (GridImageAdapter) myGridView.getAdapter();
            myAdapter.selectedImage = position;
            myAdapter.notifyDataSetChanged();
        }
    });