Why items change order upon scrolling in Android GridView?
Asked Answered
N

6

15

I have a GridView in android which I fill it with data retrieved from a xml resource.
For example I have 15 items in the GridView which are placed in order. The overall height exceeds the Screen height so i have to scroll to see the rest of the items.
The problem is when I scroll back up, the order of the invisible rows have changed. It's a mysterious behavior as sometimes items swap rows with each other. Here is my getView method:

public class ImageAdapter extends BaseAdapter {
        public ImageAdapter(Context c, NodeList cuu) {
                cu = cuu;
        }
        public int getCount() {
                Log.d("Node Count",cu.getLength()+"");
                return cu.getLength();
        }
        public Object getItem(int position) {
                return position;
        }
        public long getItemId(int position) {
                return position;
        }
        public View getView(int position, View convertView, ViewGroup parent) {
                View myView = convertView;
                if (convertView == null) {
                    Node nd = cu.item(position);
                    Log.d("nodes","Pos: "+(position)+" Name: "+nd.getNodeName()+" Title: "+nd.getAttributes().getNamedItem("title").getTextContent());
                    int catID = Integer.parseInt(nd.getAttributes().getNamedItem("id").getTextContent());
                    LayoutInflater li = getLayoutInflater();
                    myView = li.inflate(R.layout.grid_item, null);
                   ImageView imageView = (ImageView) myView.findViewById(R.id.grid_item_image);
                   myView.setLayoutParams(new GridView.LayoutParams(70, 100));
                   id.download(nd.getAttributes().getNamedItem("icon").getTextContent(),imageView);
                   TextView textView = (TextView) myView.findViewById(R.id.grid_item_text);
                   textView.setText(nd.getAttributes().getNamedItem("title").getTextContent());
                   myView.setTag((Object) catID);
                }else{
                    //Log.d("nodes","Pos: "+(position));
                }
                return myView;
        }
        private NodeList cu = null;
    }

Update: Well, it's rather odd. After some more debugging I noticed that in the GridView, the Adapter skips the 13th position, meaning it returns 1 instead of 13 and then moves on to 14!!! (I guess the 13 is bad luck!)

Neuberger answered 20/5, 2012 at 9:27 Comment(2)
The Adapter will not skip positions by itself. Post the full code for your adapter.Vitalism
Alright, I put the whole Adapter there.Neuberger
V
8

If that is all the code you have in the getView method you're not implementing it right:

public View getView(int position, View convertView, ViewGroup parent) {
        View myView = convertView;
        if (myView == null) {           
            Node nd = cu.item(position);
            int catID = Integer.parseInt(nd.getAttributes().getNamedItem("id")
                    .getTextContent());
            LayoutInflater li = getLayoutInflater();
            myView = li.inflate(R.layout.grid_item, null);
            myView.setLayoutParams(new GridView.LayoutParams(70, 100));         
            myView.setTag((Object) catID);
        } 
        Node nd = cu.item(position);
        Log.d("nodes", "Pos: " + (position) + " Name: " + nd.getNodeName()
                + " Title: "
                + nd.getAttributes().getNamedItem("title").getTextContent());
        ImageView imageView = (ImageView) myView
                .findViewById(R.id.grid_item_image);
        id.download(nd.getAttributes().getNamedItem("icon")
                .getTextContent(), imageView);
        TextView textView = (TextView) myView
                .findViewById(R.id.grid_item_text);
        textView.setText(nd.getAttributes().getNamedItem("title")
                .getTextContent());
        return myView;
    }

I don't know if the code above works, yours is a bit strange. Anyway, I think the behavior you see its normal because all you do in your adapter is populating the first visible elements and then the adapter will be reusing the exact same elements when you scroll up and down because of the recycling. In the getView you should:

  • Check if the convertView is null:
    • If it is null it's time to inflate a new View for this GridView's element. You could also use the holder pattern to cache looking for the composing Views(instead of searching with findViewById everytime)(You use the setTag element for the inflated View but it's a piece of data from the Node data element ?!? What do you plan to do with it?!?)
    • If it isn't null you'll do nothing(or if you implement the holder pattern you would get the tag with the already searched Views)

And this is what you should do in that if/else statement

  • After the part above you'll populate the Views with data(so they hold the apropriate data for that position).
Vitalism answered 20/5, 2012 at 11:49 Comment(2)
Thanks, It's because I'm totally new to Android programming. With your help I could get it to work.Neuberger
ty! you saved me a daySulphuryl
S
11
public View getView(int position, View convertView, ViewGroup parent) {
    View view;
    if (convertView == null) {
        view= inflate your xml
    } else {
        view=convertView;
    }
    // all remaining code like 
    // view.findViewById(R.id.btn).setText("MyButton");                            
    // must be outside if-else
}
Scyphate answered 8/7, 2013 at 9:48 Comment(0)
V
8

If that is all the code you have in the getView method you're not implementing it right:

public View getView(int position, View convertView, ViewGroup parent) {
        View myView = convertView;
        if (myView == null) {           
            Node nd = cu.item(position);
            int catID = Integer.parseInt(nd.getAttributes().getNamedItem("id")
                    .getTextContent());
            LayoutInflater li = getLayoutInflater();
            myView = li.inflate(R.layout.grid_item, null);
            myView.setLayoutParams(new GridView.LayoutParams(70, 100));         
            myView.setTag((Object) catID);
        } 
        Node nd = cu.item(position);
        Log.d("nodes", "Pos: " + (position) + " Name: " + nd.getNodeName()
                + " Title: "
                + nd.getAttributes().getNamedItem("title").getTextContent());
        ImageView imageView = (ImageView) myView
                .findViewById(R.id.grid_item_image);
        id.download(nd.getAttributes().getNamedItem("icon")
                .getTextContent(), imageView);
        TextView textView = (TextView) myView
                .findViewById(R.id.grid_item_text);
        textView.setText(nd.getAttributes().getNamedItem("title")
                .getTextContent());
        return myView;
    }

I don't know if the code above works, yours is a bit strange. Anyway, I think the behavior you see its normal because all you do in your adapter is populating the first visible elements and then the adapter will be reusing the exact same elements when you scroll up and down because of the recycling. In the getView you should:

  • Check if the convertView is null:
    • If it is null it's time to inflate a new View for this GridView's element. You could also use the holder pattern to cache looking for the composing Views(instead of searching with findViewById everytime)(You use the setTag element for the inflated View but it's a piece of data from the Node data element ?!? What do you plan to do with it?!?)
    • If it isn't null you'll do nothing(or if you implement the holder pattern you would get the tag with the already searched Views)

And this is what you should do in that if/else statement

  • After the part above you'll populate the Views with data(so they hold the apropriate data for that position).
Vitalism answered 20/5, 2012 at 11:49 Comment(2)
Thanks, It's because I'm totally new to Android programming. With your help I could get it to work.Neuberger
ty! you saved me a daySulphuryl
G
4

I have just deleted the

if(convertView==null)

it works

Govan answered 11/2, 2014 at 17:18 Comment(0)
C
3

Your adapter implementation is fatally flawed.

In getView(), if convertView is null, you inflate a layout and populate its widgets. This is fine.

In getView(), if convertView is not null, you do absolutely nothing. This is exceedingly wrong.

What you should be doing in getView(), if convertView is not null, is still populating the cell's widgets. convertView represents a cell to be recycled, so you can skip the inflation and save CPU time, but you still have to update that cell's widgets to reflect whatever position you are supposed to be setting up in this specific getView() call.

Conchoid answered 20/5, 2012 at 11:43 Comment(3)
im having the same problem but i applied ViewHolder, and still my order of images were changing.Jokester
@Conchoid Is it true that all actions including findViewById() must be done outside if/else? Someone stated that here and this information is new to me.Ventricose
@sandalone: On every call to getView() you need to configure the row. What if/else constructs you use, if any, is up to you, so long as that on very call to getView() you completely configure the row. In the case of the OP, the OP was not configuring the row if convertView was not null.Conchoid
P
1

If your Grid item has several views in it you can use getTag() and setTag() methods. In this case, grid items won't change their places on scrolling anymore and the performance still be fine:

public class SomeAdapter extends BaseAdapter {

    public static class ViewHolder {
        public TextView tvTitle;
        public ImageView imgPoster;
    }

    //...
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder grid;
        LayoutInflater inflator = activity.getLayoutInflater();

        if (convertView == null) {
            grid = new ViewHolder();
            convertView = inflator.inflate(R.layout.grid_item, null);
            grid.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
            grid.imgPoster = (ImageView) convertView.findViewById(R.id.img_poster);

            convertView.setTag(grid);   // <<-- H E R E
        } else {
            grid = (ViewHolder) convertView.getTag();   // <<-- H E R E
        }
        grid.tvTitle.setText(dataItem.getTitle());
        grid.imgPoster.setImage(dataItem.getImage());
        return convertView;
    }
}
Putrefaction answered 29/9, 2016 at 0:34 Comment(0)
L
0

I used this solution, based on another samples, and worked:

public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;
    if (convertView == null) {
        view = inflate your xml and apply the View Holder pattern
    } else {
        view = convertView;
    }
    // retrieve the object by Holder pattern 
    // extra code here
}
Lowell answered 2/6, 2014 at 20:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.