Question

I have a GridView whose elements are based on a FrameLayout containing an ImageView and a CheckedTextView. The xml file for the FrameLayout is as follows:

<FrameLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:id="@+id/gridImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:adjustViewBounds="false">
</ImageView>
<CheckedTextView
android:id="@+id/imageTick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal|bottom"
android:checkMark="@drawable/icon"
android:checked="false"
android:visibility="invisible"
>
</CheckedTextView>
</FrameLayout>

EDIT: This is the adapter's getView() method i use for each element:

public View getView(int position, View convertView, ViewGroup parent) {
        Log.d("getView()", position+"");
        View v;
        ImageView imageView;
        if(convertView == null) {

            v = LayoutInflater.from(mContext).inflate(R.layout.photo_text_view, null);
            v.setLayoutParams(new GridView.LayoutParams(100,100));
        }
        else
        {
            v = convertView;

        }
        imageView = (ImageView) v.findViewById(R.id.image);
        imageView.setImageBitmap(mThumbsIds.get().get(position));

        v.setPadding(8, 8, 8, 8);
        return v;
    }

In my activity class, I load a context menu and once i select an option i make the CheckedTextView visible for that GridView element like so :

GridView gridView = (GridView) findViewById(R.id.gridview);
    View selected =gridView.getChildAt(position);
    CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
    selectedCheck.setChecked(true);
    selectedCheck.setVisibility(View.VISIBLE);

So two things can potentially happen from this point:

1) Lets say I picked the element at position 0, the CheckedTextView becomes visible, but when i scroll down my GridView, another element (e.g. position 17) has its CheckedTextView visible aswell. This continues on random elements as i scroll down towards the bottom.

2) If i pick an element towards the bottom, scroll back up to the top, and run one of methods to make all CheckedTextView's invisible, a NullPointerException is thrown for the element at the bottom. This happens at this point: View selected =gridView.getChildAt(position); where selected becomes null.

Whats going on here? Why do these random CheckedTextView's become visible and why do i get exceptions?

Was it helpful?

Solution

The problems lie in the way you track checked items:

GridView gridView = (GridView) findViewById(R.id.gridview);
View selected =gridView.getChildAt(position);
CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
selectedCheck.setChecked(true);
selectedCheck.setVisibility(View.VISIBLE);

1) getChildAt will not give you the correct view if you've scrolled into content and you're indexing by adapter position. When referencing an active view in a GridView or ListView you want to do something like this:

final int index = position - gridView.getFirstVisiblePosition();
View selected = gridView.getChildAt(index);

The reason is that your GridView only keeps child views for adapter items that it is currently displaying. It may only have child views for elements 4 to 23 if elements 0 to 3 were scrolled off of the top earlier.

This is why you're getting exceptions when you do View selected =gridView.getChildAt(position); a view for that position does not actually exist when it's off-screen, so getChildAt returns null.

2) When a ListView or GridView has its content change or it otherwise needs to re-layout its child views, it does so by re-binding existing views using the convertView parameter to your adapter. Your adapter never adjusts the checked state of your item views, so a checked view can be reused for an item that should be unchecked later.

To solve this you should have your data model that your adapter presents track the checked state of your items rather than relying on the item views to do it. This means that your adapter should always verify/set the checked state of the item in getView. It may look something like this:

imageView = (ImageView) v.findViewById(R.id.image);
imageView.setImageBitmap(mThumbsIds.get().get(position));

CheckedTextView checkView = (CheckedTextView) v.findViewById(R.id.imageTick);

if (mData.isItemChecked(position)) {
    checkView.setChecked(true);
    checkView.setVisibility(View.VISIBLE);
} else {
    checkView.setVisibility(View.GONE);
}

Then when your option is selected that should change the checked state of an item, instead of the code snippet above do something like:

data.setItemChecked(position, true);
adapter.notifyDataSetChanged();

notifyDataSetChanged() will tell the GridView that it should re-bind the item views, which will have your getView method fix up the checked states properly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top