Android gridview keep item selected
Asked Answered
M

6

19

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.

Melpomene answered 4/7, 2012 at 9:19 Comment(1)
Can you provide us with some code?Bolduc
B
19

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>
Bolduc answered 4/7, 2012 at 11:17 Comment(2)
It's a bad idea to store the state checked in a business (model) class. The view displaying the data should take care of it. Besides, switching the background in the adapter and onClickListener looks like a hack. GridView can do most of the work for you, why not let it? (See my answer.)Epiphora
But this will work on scrolling grid/list views, while storing checked state in the view won't, because views inside grids in Android are recycled when scrolling occurs.Wetzel
E
52

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>
Epiphora answered 30/6, 2013 at 11:27 Comment(9)
How would you apply your code to a GridView containint images?Newbold
Create a new class and inherit ImageView instead of RelativeLayout. You can apply this code to whatever 'View' you want, but make sure that the styling is working. Maybe an animation will be more appropriate for images, instead of changing background colour.Epiphora
Doesn't work with StickyHeadersGridView... And it's a great library and worth do this manually. But thats the best practice =)Disappearance
how do i listen to the event of oncheck of the layout?Enunciate
Just add OnCLickListener to it and check if it is checked/unchecked.Epiphora
Note if you don't need to reuse this, you can make an anonymous class within getView, overriding onCreateDrawableState as above, but replace isChecked() there with ((AbsListView)parent).isItemChecked(position).Assuasive
The views are destroyed and recreated when scrolling a gridview, so this approach won't work... When the grid would be scrolled previously selected item may be destroyed/recycled and check would appear in unexpected subviews of this grid. In my case on Android 2.3.3 I saw it in the wild.Wetzel
Note that in my project, all I needed was a text field with an alternate background color (drawable) when selected. By using a CheckedTextView instead of wrapping a TextView in a (custom) layout, I didn't have to do anything special at all. Very simple solution.Chirr
@southerton I've verified that your comment is false. The approach does work perfectly with item recycling. The checked state of cells is correctly restored/removed by the native recycling code.Spectacled
B
19

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>
Bolduc answered 4/7, 2012 at 11:17 Comment(2)
It's a bad idea to store the state checked in a business (model) class. The view displaying the data should take care of it. Besides, switching the background in the adapter and onClickListener looks like a hack. GridView can do most of the work for you, why not let it? (See my answer.)Epiphora
But this will work on scrolling grid/list views, while storing checked state in the view won't, because views inside grids in Android are recycled when scrolling occurs.Wetzel
A
6

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;
    }
}
Assuasive answered 25/10, 2013 at 21:26 Comment(0)
F
5

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();
        }
    });
Forint answered 20/10, 2015 at 20:29 Comment(2)
In addition you can do a lot of other things with the selected image for example if you want it colored just use: imageView.setBackgroundColor(Color.BLUE);Forint
+1 Great approach, simple and nice! However, I prefer to change selectedImage and notify the change directly inside the adapter, but more important, I've choosen to create the imageview once in the first call of getView and reuse it for other images (as ViewHolder pattern). The advantage is that the adapter don't need to create multiple views for each items. It suits perfect with your answer.Hypaethral
C
1

the views in the gridview must be CheckBoxs, in this way you can check and uncheck them .

Chev answered 4/7, 2012 at 9:32 Comment(2)
how can i add checkboxes to gridview?Freckly
See youtube.com/watch?v=wDBM6wVEO70 to get you started. ListView and GridView work in the same way.Epiphora
C
0
**You can add tag and check for tag**


 gv.setOnItemClickListener((adapterView, view, i, l) -> {

                int f = gv.getCheckedItemPosition();

                if(view.getTag()=="selected")
                {
                    view.setTag("notselected");
                    String clickedText = gv.getItemAtPosition(i).toString();
                    filterKeywords.remove(clickedText);
                    view.setBackgroundColor(Color.WHITE);
                }
                else
                {
                    view.setTag("selected");
                    String clickedText = gv.getItemAtPosition(i).toString();
                    filterKeywords.add(clickedText);
                    view.setBackgroundColor(Color.GREEN);

                }

                System.out.println("KEYWORDS"+filterKeywords);


            });
Crashland answered 8/8, 2018 at 8:8 Comment(1)
You might like How do I compare strings in Java?Yu

© 2022 - 2024 — McMap. All rights reserved.