Android: Not sure why my listview is getting all jumbled up?
Asked Answered
T

3

1

I implemented a ListView that worked correctly until I added more than 5 items and 2 headers. Im not entirely sure why some items are not appearing and others are appearing multiple times. Any assistance in fixing this will be much appreciated. Code is include below.

Toolbox.java

public class Toolbox extends Fragment {
    private ListView lstView;
    private View rootView;
    List<Tools> tools;

    public static Toolbox newInstance(Context context) {
        Toolbox fragment = new Toolbox();
        return fragment;
    }

    public Toolbox() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        ((MainActivity) getActivity()).setActionBarTitle("Technical Toolbox");
        rootView = inflater.inflate(R.layout.fragment_toolbox, container, false);
        lstView =(ListView) rootView.findViewById(R.id.list);
        final FragmentActivity c = getActivity();
        //final RecyclerView recyclerView = (RecyclerView) recView.findViewById(R.id.reView);


        setToolBoxData();
        setToolBoxAdapter();
        return rootView;
    }

    private void setToolBoxAdapter() {
        ListArrayAdapter adapter = new ListArrayAdapter(getActivity(),tools);
        lstView.setAdapter(adapter);
    }

    private void setToolBoxData(){
        tools=new ArrayList<>();
        tools.add(new Header("Languages"));
        tools.add(new ListItem("Android",R.drawable.ic_android));
        tools.add(new ListItem("XML", R.drawable.ic_xml));
        tools.add(new ListItem("Java",R.drawable.ic_java));
        tools.add(new ListItem("JavaScript", R.drawable.ic_javascript));
        tools.add(new ListItem("C++", R.drawable.ic_cpp));
        tools.add(new ListItem("Visual Basic", R.drawable.ic_vb));
        tools.add(new ListItem("HTML", R.drawable.ic_html));
        tools.add(new ListItem("CSS", R.drawable.ic_css));
        tools.add(new Header("Source Control"));
        tools.add(new ListItem("Git", R.drawable.ic_git));
        tools.add(new ListItem("GitHub", R.drawable.ic_github_cat));
        tools.add(new ListItem("SourceTree", R.drawable.ic_sourcetree));
        tools.add(new ListItem("BitBucket", R.drawable.ic_bitbucket));
        tools.add(new Header("DataBase"));
        tools.add(new ListItem("Parse", R.drawable.ic_parse));
        tools.add(new ListItem("MS Access", R.drawable.ic_access));
        tools.add(new ListItem("SQL", R.drawable.ic_sql));
        tools.add(new Header("Design & IDE Tools"));
        tools.add(new ListItem("Android Studio", R.drawable.ic_androidstudio));
        tools.add(new ListItem("Visual Studio", R.drawable.ic_visual_studio));
        tools.add(new ListItem("Genymotion", R.drawable.ic_genymotion));
        tools.add(new ListItem("Ionic", R.drawable.ic_ionic));
        tools.add(new Header("Office Tools"));
        tools.add(new ListItem("MS Project", R.drawable.ic_project));
        tools.add(new ListItem("MS Visio", R.drawable.ic_visio));
        tools.add(new ListItem("MS Excel", R.drawable.ic_excel));
        tools.add(new ListItem("MS Word", R.drawable.ic_word));
    }
}

ListArrayAdapter.java

public class ListArrayAdapter extends ArrayAdapter<Tools> {
    private LayoutInflater mInflater;

    public enum RowType{ LIST_ITEM, HEADER_ITEM }

    public ListArrayAdapter(Context context, List<Tools> tools){
        super(context, 0, tools);
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public int getViewTypeCount(){return RowType.values().length;}

    @Override
    public int getItemViewType(int pos){return getItem(pos).getViewType();}

    private static final int TYPE_ITEM = 0;
    private static final int TYPE_SEPARATOR = 1;

    public View getView(int position, View convertView, ViewGroup parent)  {
        ViewHolder holder = null;
        int rowType = getItemViewType(position);
        View View;
        if (convertView == null) {
            holder = new ViewHolder();
            switch (rowType) {
                case TYPE_ITEM:
                    convertView = mInflater.inflate(R.layout.listview_item, null);
                    holder.View=getItem(position).getView(mInflater, convertView);
                    break;
                case TYPE_SEPARATOR:
                    convertView = mInflater.inflate(R.layout.list_title, null);
                    holder.View=getItem(position).getView(mInflater, convertView);
                    break;
            }
            convertView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) convertView.getTag();
        }
        return convertView;
    }

    public static class ViewHolder {
        public  View View;
    }
}

Tools.java

public interface Tools {
    public int getViewType();
    public View getView(LayoutInflater inflater, View convertView);
}

ListItem.java

public class ListItem implements Tools {
    private final String str1;
    private final int pic1;

    public ListItem(String text1, int pic1) {
        this.str1 = text1;
        this.pic1 = pic1;
    }

    @Override
    public int getViewType() {
        return ListArrayAdapter.RowType.LIST_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        View view;
        if (convertView == null) {
            view = (View) inflater.inflate(R.layout.listview_item, null);
            // Do some initialization
        } else {
            view = convertView;
        }

        TextView text1 = (TextView) view.findViewById(R.id.listText);
        ImageView picture1 = (ImageView) view.findViewById(R.id.listIcon);
        text1.setText(str1);
        picture1.setImageResource(pic1);

        return view;
    }
}

Header.java

public class Header implements Tools {
    private final String         name;

    public Header(String name) {
        this.name = name;
    }

    @Override
    public int getViewType() {
        return ListArrayAdapter.RowType.HEADER_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        View view;
        if (convertView == null) {
            view = (View) inflater.inflate(R.layout.header, null);
            // Do some initialization
        } else {
            view = convertView;
        }

        TextView text = (TextView) view.findViewById(R.id.separator);
        text.setText(name);

        return view;
    }
}
Target answered 23/6, 2015 at 20:5 Comment(0)
S
1

You are only populating your row widgets in the if (convertView == null) case. If convertView is not null, you are simply returning it unchanged, meaning it will have data from some prior position, not the position that is being requested.

IOW, you need to call methods like setText() and setImageResource() on every getView() call, to fill in the row's widgets with the data for the requested position.

Saucier answered 23/6, 2015 at 20:17 Comment(3)
so what your saying is that i need to get rid of the if (convertView==null) case and just have my switch statement?Target
@8BitYoda: Possibly. Basically, you seem to be delegating inflation and view population to the getView() methods of Header and ListItem. In that case, the getView() at the adapter should only pass control to Header and ListItem. Those classes can then decide whether to inflate or recycle, and then populate the widgets in either case.Saucier
Removing the 'if' 'else' statement fixed it. Thanks for pointing this out. When I coded this I half followed a guide and half went from memory so I imagine that's where I was getting crossed up. Thanks again!Target
A
2

Brief background:

Actually I came through this same error today. As it is described I've also done 90-95% identical implementation. However I saw the accepted answer of this Q&A thread also. But I couldn't agree with you! So I searched and searched why my simpler ArrayAdapter implementation wasn't working properly. At the first time it loads Okay. But soon after I scroll it once? All the types of rows go jumbled-bumbled!

Summarized Answer:
Read the Android Developer Doc for further information you might be find the clue directly. To implement multiple types of Views for ListView's rows we have to essentially implement, getItemViewType() and getViewTypeCount() methods. And getItemViewType() documentation gives us a Note as follows:

Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.

So in your getItemViewType() you should return values for the View Type, starting from 0, to the last type as (number of types - 1). For example, let's say you only have two types of views? So depending on the data object for the view, you could only return 0 or 1 in the getItemViewType() method.

If you didn't get the clue or couldn't still resolve and need further information please read it down the bellow:

Detailed Explanation:

According to the Android Developer Documentation or the API Doc, it has mentioned that in order to implement multiple types of views within ListView adapter, we have to implement the following methods:

public int getItemViewType (int position)
public int getViewTypeCount ()

And out of the above two methods, getItemViewType is essential in our case (where we have multiple view types). That's true right? So what?

That's where me and @8BitYoda has gone wrong. The OP's wrong thing was he had defined an Enum where it doesn't give types as range of integers within the range of 0 to (getViewTypeCount() - 1).

And what I had done was, I had defined the types as follows:

private static final int TYPE_ITEM = 1;
private static final int TYPE_ITEM = 2;

And at first I haven't implemented, getViewTypeCount(), so I implemented it as follows:

public int getViewTypeCount() {
      return 2;
}

Then soon after I run the app and try to load that view with the ListView, it crashed giving me the following exception:

java.lang.ArrayIndexOutOfBoundsException: length=2; index=2 at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6643) at android.widget.ListView.measureHeightOfChildren(ListView.java:1292) at android.widget.ListView.onMeasure(ListView.java:1188) :
:
:
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Say what? :O

It indirectly says that it's related with the integer I'm returning from the method getViewTypeCount(), as I'm returning 2. And I return 2 because I have only two types. So I went back to the Android Documentation seeking the final help. And it really helped me and resolved my question, by reading the doc which relates to the methods I have implemented.

For a quick snap, have a look at the image bellow: enter image description here

With a tiny little font, it has given a NOTE for us.


What does the note of getItemViewType (int position) method says?

Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.

So right after seeing this I understood that I need to re-assign the type integers I'm sending to starting from 0 to whatever the number of types I have, like zero based array index. So I re-corrected those two lines and it worked like a charm afterwards!

private static final int TYPE_ITEM = 0;
private static final int TYPE_ITEM = 1;

Sorry, I know my answer went a little longer than I expected, almost like a mini tutorial. But it's okay for me I spent around an hour+ to write this because I'd to spend around 5 hours to figure out this silly mistake. Who thinks that types should be assigned starting from 0, like 0-based array index?

So I hope my answer might be helpful to somebody out there to resolve this error.

Cheers!!!

Aland answered 27/4, 2016 at 17:46 Comment(0)
S
1

You are only populating your row widgets in the if (convertView == null) case. If convertView is not null, you are simply returning it unchanged, meaning it will have data from some prior position, not the position that is being requested.

IOW, you need to call methods like setText() and setImageResource() on every getView() call, to fill in the row's widgets with the data for the requested position.

Saucier answered 23/6, 2015 at 20:17 Comment(3)
so what your saying is that i need to get rid of the if (convertView==null) case and just have my switch statement?Target
@8BitYoda: Possibly. Basically, you seem to be delegating inflation and view population to the getView() methods of Header and ListItem. In that case, the getView() at the adapter should only pass control to Header and ListItem. Those classes can then decide whether to inflate or recycle, and then populate the widgets in either case.Saucier
Removing the 'if' 'else' statement fixed it. Thanks for pointing this out. When I coded this I half followed a guide and half went from memory so I imagine that's where I was getting crossed up. Thanks again!Target
H
1

Rather than using positions, you should use some form of id for uniqueness.

Also as the views are recycled, you need to update them with new data.

if (convertView == null) {
        holder = new ViewHolder();
        switch (rowType) {
            case TYPE_ITEM:
                convertView = mInflater.inflate(R.layout.listview_item, null);
                holder.View=getItem(position).getView(mInflater, convertView);
                break;
            case TYPE_SEPARATOR:
                convertView = mInflater.inflate(R.layout.list_title, null);
                holder.View=getItem(position).getView(mInflater, convertView);
                break;
        }
        convertView.setTag(holder);
    }
    else
    {
        holder = (ViewHolder) convertView.getTag();
    //here update the holder in else case also.
    }

    return convertView;
Humidify answered 23/6, 2015 at 20:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.