9

I've got a strange problem that drives me crazy. In my android application I customized my own adapter that extends from ArrayAdapter. The items of the ListView to which I added my adapter can either be labeled-text (not editable), editable text or a spinner. The crazy stuff is: when I scroll the ListView, there are two problems:

(1) the (selected) value that is shown in the spinner items changes sometimes although I only scrolled!! when I click on the spinner, the old selected value is still shown (the one, that should be shown by the spinner) (2) the order of the ListViewItems changes when I scroll!

=> BUT the data in the adapter doesn't change (neither the data itself nor the order) - so it must be a problem of the View itself?! maybe android caches in background and doesn't refresh the ListViewItems soon enough or sth like that?!

Can anybody help me please?

Thx a lot!

Ok, I have found a solution that is not very nice, but it works. I simply did not use convertView anymore although that is suboptimal regarding to memory and performance. In my case it should be ok because my ListView's maximal amount of items is 15. Here is my Adapter-Class:

 public class FinAdapter extends ArrayAdapter<Param>{
    public Param[] params;
    private boolean merkzettel;
    protected EditText pv;

    public FinAdapter(Context context, int textViewResourceId, Param[] items, boolean merkzettel) {
        super(context, textViewResourceId, items);
        this.params = items;
        this.merkzettel = merkzettel;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final Param p = params[position];
        if(p != null){
            if(merkzettel){
                if (convertView == null) {
                    LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    convertView = vi.inflate(R.layout.merk_det_item, null);
                }
                TextView tvl = (TextView) convertView.findViewById(R.id.paramM);
                TextView edl = (TextView) convertView.findViewById(R.id.param_valueM);
                TextView pal = (TextView) convertView.findViewById(R.id.param_unitM);
                if (tvl != null) {
                    tvl.setText(p.getName());                            
                }
                if(pal != null){
                    pal.setText(p.getUnit());
                }
                if(edl != null){
                    edl.setText(p.getDefData());
                }
            }
            else{
                if(p.isSelect()){

                if (convertView == null) {
                    LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    convertView = vi.inflate(R.layout.fin_prod_list_item_select, null);
                }            
                TextView tvs = (TextView) convertView.findViewById(R.id.paramS);
                Spinner sp = (Spinner) convertView.findViewById(R.id.spinner_kalk);
                TextView paU = (TextView) convertView.findViewById(R.id.param_unitS);


                if (tvs != null) {
                    tvs.setText(p.getName());                            
                }
                if(paU != null){
                    paU.setText(p.getUnit());
                }
                if(sp != null){
                    String[] values = new String[p.getData().size()];
                    for(int i=0; i<values.length; i++){
                        values[i] = p.getData().get(i);
                    }
                    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this.getContext(), android.R.layout.simple_spinner_item, values);
                    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                    sp.setAdapter(adapter);
                    sp.setSelection(p.getData().indexOf(p.getDefData()));
                    sp.setOnItemSelectedListener(new OnItemSelectedListener(){
                        public void onItemSelected(AdapterView<?> parent,
                                View convertView, int pos, long id) {
                            p.setDefData(p.getData().get(pos));
                            p.setChanged(true);
                        }
                        public void onNothingSelected(AdapterView<?> arg0) {
                            // TODO Auto-generated method stub

                        }

                    });
                }
            }       
            else if(p.isEdit()){
                if (convertView == null) {
                    LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    convertView = vi.inflate(R.layout.fin_prod_list_item_edit, null);
                }
                TextView pa = (TextView) convertView.findViewById(R.id.param);
                pv = (EditText) convertView.findViewById(R.id.param_value);
                TextView paE = (TextView) convertView.findViewById(R.id.param_unit);
                if (pa != null) {
                    pa.setText(p.getName());                            
                }
                if(paE != null){
                    paE.setText(p.getUnit());
                }
                if(pv != null){
                    pv.setText(p.getDefData());
                    pv.setOnEditorActionListener(new OnEditorActionListener(){
                        public boolean onEditorAction(TextView convertView, int actionId,
                                KeyEvent event) {
                            // TODO Auto-generated method stub
                            p.setDefData(pv.getText().toString());
                            p.setChanged(true);
                            return false;
                        }                   
                    }); 
                }

            }
            else if(p.isLabel()){
                if (convertView == null) {
                    LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    convertView = vi.inflate(R.layout.fin_prod_list_item_label, null);
                }
                TextView tvl = (TextView) convertView.findViewById(R.id.paramL);
                TextView edl = (TextView) convertView.findViewById(R.id.param_valueL);
                TextView pal = (TextView) convertView.findViewById(R.id.param_unitL);
                if (tvl != null) {
                    tvl.setText(p.getName());                            
                }
                if(pal != null){
                    pal.setText(p.getUnit());
                }
                if(edl != null){
                    edl.setText(p.getDefData());
                }   
            }}
        }
        return convertView;
    }
}
Shruti
  • 1
  • 13
  • 55
  • 95
user1384200
  • 109
  • 1
  • 1
  • 7
  • Android recycles views to wisely conserve resources, but other than that *we cannot help you without seeing the relevant code*. You can learn about View recycling here: [Turbo-charge your UI: How to Make your Android UI Fast and Efficient](http://www.google.com/events/io/2009/sessions/TurboChargeUiAndroidFast.html) from the man who wrote it. – Sam Aug 15 '12 at 15:56
  • thx - I will have a look at it! – user1384200 Aug 15 '12 at 16:27

3 Answers3

5

I had a similar problem with multiple item types in the list. In my case the list item was either a section (label) item or a common list item.

To work with such types of lists, you should override getViewTypeCount and getItemViewType methods. Something like this:

private static final int ITEM_VIEW_TYPE_ITEM = 0;
private static final int ITEM_VIEW_TYPE_SEPARATOR = 1;

@Override
public int getViewTypeCount() {
    return 2;
}

@Override
public int getItemViewType(int position) {
    return this.getItem(position).isSection() ? ITEM_VIEW_TYPE_SEPARATOR : ITEM_VIEW_TYPE_ITEM;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    final Item item = this.getItem(position);

    if (convertView == null) {
        convertView = mInflater.inflate(item.isSection() ? R.view1 : R.view2, null);
    }

    if(item.isSection()){
        //...
    }
    else{
        //...
    }

    return convertView;
}

Then the convertView parameter will always be correct and contain that type which you need.

And another thing: you explicitly added the public Param[] params field when you already have it in the base class ArrayAdapter<Param>.

I would recommend to inherit from the BaseAdapter class.

Edit: Here is the code which you can try to use in order to make your Spinner work:

sp.setTag(p);
sp.setOnItemSelectedListener(new OnItemSelectedListener(){
public void onItemSelected(AdapterView<?> parent, View convertView, int pos, long id) {
    Param currentItem = (Param)parent.getTag();
    currentItem.setDefData(currentItem.getData().get(pos));
    currentItem.setChanged(true);
}
//...

Try to use the combination of the getTag and setTag methods. I remember that I had similar problems with event handlers and final variables, but I completely forgot the cause of them, so I can't explain exactly why this happens.

vortexwolf
  • 13,967
  • 2
  • 54
  • 72
  • Sounds great - I will try it out on Monday and give feedback wether it worked out for me. Thank u very much so far =) – user1384200 Aug 16 '12 at 12:32
  • Ok, I tried it out. It works fine for the ordering of my list items - **they are not mixed any more**. But I still have the problem, that my spinners as list items **show the first value** of their items after scrolling **although I have chosen another item** (not the first one). For example if I have a ListViewItem that is a Spinner with {1,2,3} as items. Let's say that "1" is selected. Now I select "2" and scroll down my ListView. After scrolling up again the Spinner shows "1" instead of "2". But if I click on the Spinner to select another value it shows that "2" is chosen... Why? – user1384200 Aug 20 '12 at 18:45
  • @user1384200 You speak about the Spinner inside the second 'if' block? I think that the reason is in the code `p.setDefData`, you use the external variable `p` inside the event handler. Though it is marked as `final`, such variables don't work as expected and I had lot of problems with them. You should check it with the debugger, put the breakpoint inside the event handler and look at the value of `p`. I will edit my response now and show what you can do with this variable. – vortexwolf Aug 20 '12 at 19:21
  • I debugged as you said. The defData-parameter is set right - so I don't think that this causes my problem. That makes sense in regard to the Spinner showing the right item when clicking on it (that means in the List with the Radiobuttons where the possible values to select are shown the right is selected although it is not shown as selected when you only look at the spinner (not expanded). – user1384200 Aug 20 '12 at 19:47
  • humph... currentItem is null although it is set properly – user1384200 Aug 20 '12 at 20:21
  • @user1384200 My mistake, you should call `parent.getTag`. Also make sure that you always call `sp.setSelection` when the `getView` method is executed with the spinner view. – vortexwolf Aug 20 '12 at 20:25
  • t still doesn't work. I changed it to the following: `public void onItemSelected(AdapterView> parent, View cv, int pos, long id) { Param currentItem = (Param)parent.getTag(); currentItem.setDefData(currentItem.getData().get(pos)); currentItem.setChanged(true); parent.setSelection(currentItem.getData().indexOf(p.getDefData())); }` – user1384200 Aug 20 '12 at 20:38
  • @user1384200 Remove `sp.setSelection` from the listener. You need to check this line before you set the listener. It seems that indexOf returns incorrect value and setSelection doesn't work as expected. – vortexwolf Aug 21 '12 at 07:49
1

As mentioned by Sam, Android system always tries to recycle if possible to conserve resources. That means, developers need to keep track of the View on ListView themselves. For the entity, or so-called the presentation data structure, you can have something to keep track of the selected/de-selected state, for example:

class PresentationData {
    // other stuffs..
    boolean isSelected = false;
}

Mapping these data structures to the Adapter, and if any item clicked, set the state to true: listPresentationData.get(selected_position).isSelected = true

Ok so in the getView() of the adapter, keep track the presentation correctly for your data.

if(listPresentationData.get(position).isSelected) 
{ 
    // set background color of the row layout..or something else 
}

Also, worth to mention is better to have a memory cache for every views, usually called ViewHolder to improve performance as well.

Pete Houston
  • 14,931
  • 6
  • 47
  • 60
  • Thank you for your answer! I don't have a problem with noticing when an item was clicked and how to handle that. That is implemented and works fine. My problem is, that the ListView displays data when I scrolled that is not represented like that in my adapter. For example suddenly Item1 is displayed at the end of the ListView when I scrolled although it was on the top before I scrolled. – user1384200 Aug 15 '12 at 16:25
  • Oh well, if it's happened like you said, the order of items are displayed at wrong position; then it means `Param[] items` (in constructor), the original array of `items` has been changed somewhere; because `ListView` by default will displayed items according to the order stored in `items` array. – Pete Houston Aug 15 '12 at 16:45
  • no, the array did not change. after reading different forums I suppose the problem must be the getView()-Method in the Adapter-Class. I've got a similar problem as described here: [link](http://stackoverflow.com/questions/2955218/listview-in-arrayadapter-order-gets-mixed-up-when-scrolling) – user1384200 Aug 15 '12 at 17:35
0

As everyone says, Android recycle the views. What does that mean? Imagine you have 10 items in your list, but only 3 of them are displayed on the screen. When scrolling down, the first will disappear and the forth will appear. The fourth item was already in the Android system, but the fifth was created from the first item, using the same view. In my case, I had a TextView with a default background. But if some condition was met, I change the background.

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ...
    if (someCondition) {
        holder.textView.setBackground(R.drawable.different_background)
    }
    ...
}

The problem was that I don't set back the default background if the condition is not met (as I thought that the view was created for each item). The problem appeared when the first item had the condition true, so the recycled view was used with the different_background instead of the default background. To avoid the problem, I had to always set both branches of the condition and set the default values as follows:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ...
    if (someCondition) {
        holder.textView.setBackground(R.drawable.different_background)
    } else {
        holder.textView.setBackground(R.drawable.default_background)
    }
    ...
}
Sergio Lema
  • 1,491
  • 1
  • 14
  • 25