4

I have a GridView with a bunch of icons and I need to select one. And by select I mean:

  • I need the drawable id to store into a database (so I can access it later)
  • I need to draw some kind of visual cue on the grid to let the user know which icon is currently selected

I found this:
http://groups.google.com/group/android-developers/browse_thread/thread/f08a58167dbaa8c8

So I guess setSelection is out of the picture and I can't use it to select the icon itself nor draw the visual cue. I know the grid item position in the onItemClickListener and I can save it as a private class variable. My biggest problem is drawing that visual cue.

How can I achieve that? How can I draw a visual cue on the clicked item and if the user clicks different items, to "undraw" that visual cue and redraw it in the new item?

rfgamaral
  • 16,546
  • 57
  • 163
  • 275
  • i'd like to see the answer to this. I believe, that if you want some kind of selection cue, you need a focusable object. However, with a focusable object, attaching OnItemClickListener to the GridView does not work. Rather, you must individually attach an OnClickListener to each item at getView() in the adapter. – Ian Sep 23 '11 at 14:00
  • I assume that attaching `OnClickListener` to each item is not really viable, or is it? That could be a possible solution, so if you are up to it, you can do a proper answer below. I wouldn't mind. – rfgamaral Sep 23 '11 at 15:41

2 Answers2

4

After tackling with this for a couple of hours I think I finally found the solution I was looking for. Although the answers are barely related, the initial edits on Ian solution helped me find this solution :)

I'm not going to explain everything I did, I think it's pretty self explanatory. Just a few remarks:

  • First I tried view.Invalidate() and view.postInvalidate() instead of iconAdapter.notifyDataSetChanged() but neither worked. The documentation stated that the invalidate methods only "asked" to redraw the view "when possible".

  • I was looking for a simpler solution than to merge two drawables. For instance, draw the icon on the ImageView background and the visual cue as the image. For some strange reason, the visual cue started to show randomly all over the other icons when the GridView was scrolled. I don't understand why, but the idea of a visual cue on top of a background image makes perfect sense to me and ImageView allows that, no need for that extra merge method. However, I couldn't make it work without it...

MyActivity.java

public class MyActivity extends Activity {
    private GridView mGridViewIcon;

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

        mGridViewIcon = (GridView)findViewById(R.id.gridview_icon);
        mGridViewIcon.setAdapter(new IconAdapter(this));

        mGridViewIcon.setOnItemClickListener(new GridView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                IconAdapter iconAdapter = (IconAdapter)parent.getAdapter();

                iconAdapter.setSelectedItemPosition(position);
                iconAdapter.notifyDataSetChanged();
            }

        });
    }

}

IconAdapter.java

public class IconAdapter extends BaseAdapter {

    private int mSelectedPosition;
    private Integer[] mThumbIds;
    private int mIconSize;
    private Context mContext;

    public IconAdapter(Context context) {
        mThumbIds = AppHelper.ICON_SET.keySet().iterator().next();
        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.default_icon_size);
        mContext = context;
    }

    @Override
    public int getCount() {
        return mThumbIds.length;
    }

    @Override
    public Object getItem(int position) {
        return mContext.getResources().getDrawable(mThumbIds[position]);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;

        if(convertView == null) {
            imageView = new ImageView(mContext);
            imageView.setLayoutParams(new GridView.LayoutParams(mIconSize, mIconSize));
        } else {
            imageView = (ImageView)convertView;
        }

        if(mSelectedPosition == position) {
            imageView.setImageDrawable(mergeDrawableLayers(mThumbIds[position],
                    R.drawable.ic_note_selected_mark));
        } else {
            imageView.setImageResource(mThumbIds[position]);
        }

        return imageView;
    }

    public void setSelectedItemPosition(int position) {
        mSelectedPosition = position;
    }

    private Drawable mergeDrawableLayers(int background, int overlay) {
        Drawable[] drawableLayers = new Drawable[2];

        drawableLayers[0] = mContext.getResources().getDrawable(background);
        drawableLayers[1] = mContext.getResources().getDrawable(overlay);

        return new LayerDrawable(drawableLayers);
    }

}
rfgamaral
  • 16,546
  • 57
  • 163
  • 275
1

I believe, that if you want some kind of selection cue, you need a focusable object. However, with a focusable object (such as a Button), attaching OnItemClickListener to the GridView does not work (if i remember correctly). Rather, you must individually attach an OnClickListener to each item at getView() in the adapter.

Adapter:

// create a new ImageView for each item referenced by the Adapter
    public View getView(int position, View convertView, ViewGroup parent) {
        Button button;
        if (convertView == null) {  // if it's not recycled, initialize some attributes
            button = new Button(mContext);
            // set layout params (make sure its GridView.layoutParams)
            // and other stuff

        } 
        else {
            button = (Button) convertView;
        }

        button.setBackgroundResource(mThumbIds[position]); // mThumbIds hold Resource Ids
        button.setOnClickListener(new OnClickListener() {
           onClick(View v) {
             // store directly to database here, or send it with the activity with sharedPreferences (below)

             // We need an Editor object to make preference changes.
             // All objects are from android.context.Context
             SharedPreferences settings = getSharedPreferences("MY_PREFERENCE", 0);
             SharedPreferences.Editor editor = settings.edit();
             editor.putInt("button_id", mThumbIds[position]);

             // Commit the edits!
             editor.commit();
           } 
        });
        return button;
    }
}

On Activity Side, save button onClickListener:

onClick(View v) {
// Restore preferences
       SharedPreferences settings = getSharedPreferences("MY_PREFERENCE", 0);
       int id = settings.getInt("button_id", -1);

       // now safe all stuff to database
}

There may be details missing because a Button is focusable, but i think this should do. Also , you will achieve the selection by using a .xml defined selector resource. That, however, should be addressed in a separate question.

Edit 1: Actually now that i think about it, i'm not sure if a drawable .xml (the selector) can have an ID. I'll have to implement this at home later on and try it.

Edit 2: I added the sharedPreference part

Edit 3: Added activity side querying of sharedPreference.

Ian
  • 3,500
  • 1
  • 24
  • 25
  • I see what you mean know, I'm just not sure about one thing. The GridView is just one element of my Activity, I have other stuff there too, like EditText widgets. Then there's a button which is the one responsible for storing all the data into the database, including the id from the GridView item. I'm not sure how to handle that. – rfgamaral Sep 23 '11 at 17:06
  • so you only want to store the information with a "save" button? This will make the answer just beyond the scope of the question, i believe. Perhaps you can use a sharedPreference to save the item id, then query the preference in the activity. – Ian Sep 23 '11 at 18:14
  • The "store the information" part is not relevant at all, what I meant was that I needed the position of the GridView item clicked stored in some variable in the Activity holding that GridView. If I have that, the save is trivial (I assume, I'm learning Android as I go and haven't yet reached that part yet). But your previous answer (before the 2nd edit) gave me a few ideas which I have been working on these past hours. I think I've found a solution for my problem, I'll post my own answer when get I the chance in an hour or so. – rfgamaral Sep 23 '11 at 18:32
  • well just query the sharedPreference in your activity. It is very easy. – Ian Sep 23 '11 at 18:38
  • I don't like to do things that I don't understand. And honestly, it seems too much just to store an integer. – rfgamaral Sep 23 '11 at 19:10
  • alas, the easiest would be to use an onitemclicklistener then, and abandon the selection requirement. – Ian Sep 23 '11 at 19:16
  • I think I finally did it... Going to post my answer in a few minutes! – rfgamaral Sep 23 '11 at 20:27