0

I have a listview with custom adaptor with a toggle button, a spinner and some other views in it.

This listview shows more items than can normally fit on 1 screen and this causes a weird problem which I don't understand.

For example: When I press the toggle button of the first item in the list, the background changes color. If I then scroll downwards to the "2nd screen" (items 10-18) I notice that the 11th item has also been "toggled" as the background has also changed. If I then scroll down even further downwards to the 20th item has also been selected.

My adapter looks like this:

public class ArticlesListAdapter extends ArrayAdapter<Line> {

private List<Line> lineList;
private Context context;

public ArticlesListAdapter(Context context, int textViewResourceId,
        List<Line> objects) {
    super(context, textViewResourceId, objects);
    this.lineList = objects;
    this.context = context;
}

public int getCount() {
    return lineList.size();
}

public Line getItem(int position) {
    return lineList.get(position);
}

public long getItemId(int position) {
    return position;
}

public List<Line> GetAllLines() {
    return lineList;

}

@SuppressLint("InflateParams")
public View getView(int position, View convertView, ViewGroup parent) {

    Viewholder viewholder;

    if (convertView == null) {
        viewholder = new Viewholder();
        convertView = LayoutInflater.from(context).inflate(
                R.layout.articles_list_row, null);

        viewholder.articleName = (TextView) convertView
                .findViewById(R.id.textArticleName);
        viewholder.articleAmount = (EditText) convertView
                .findViewById(R.id.textArticleAmount);
        viewholder.articleButton = (ToggleButton) convertView
                .findViewById(R.id.toggleArticleReturn);
        viewholder.articleStock = (Spinner) convertView
                .findViewById(R.id.spinStockWarehouse);

        convertView.setTag(viewholder);
    } else {
        viewholder = (Viewholder) convertView.getTag();
    }

    viewholder.articleButton.setTag(position);

    if (lineList.get(position).getLineCode().length() > 0) {
        viewholder.articleButton.setOnClickListener(RetourArticleListener);
        viewholder.articleButton.setOnLongClickListener(RetourCertainAmountArticleListener);
    } else {
        viewholder.articleButton.setText("Delete");
        viewholder.articleButton.setOnClickListener(DeleteArticleListener);
    }

    String articleNameString = lineList.get(position)
            .getLineArticleDescription();
    if (articleNameString.length() > 30) {
        articleNameString.substring(0, 30);
    }
    viewholder.articleName.setText(articleNameString);
    viewholder.articleName.setTextSize(12);

    viewholder.articleAmount.setTag(position);
    viewholder.articleAmount.setTextSize(12);
    viewholder.articleAmount.setText(lineList.get(position)
            .getLineArticleAmount().toString());

    if (lineList.get(position).isLineArticleIsOriginal()) {
        viewholder.articleStock.setEnabled(false);
    }
    viewholder.articleStock.setSelection(Integer.parseInt(lineList
            .get(position).getLineArticleStock().toString()) - 1);
    viewholder.articleStock.setTag(position);
    viewholder.articleStock
            .setOnItemSelectedListener(new OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> adapterView,
                        View view, int position, long id) {
                    final int listPosition = (Integer) adapterView.getTag();
                    lineList.get(listPosition).setLineArticleStock(
                            String.valueOf(position + 1));
                }

                @Override
                public void onNothingSelected(AdapterView<?> adapter) {
                }
            });

    // we need to update adapter once we finish with editing
    viewholder.articleAmount
            .setOnFocusChangeListener(new OnFocusChangeListener() {
                public void onFocusChange(View v, boolean hasFocus) {
                    if (!hasFocus) {
                        final int position = (Integer) v.getTag();
                        final EditText articleAmount = (EditText) v;
                        lineList.get(position).setLineArticleAmount(
                                Double.parseDouble(articleAmount.getText()
                                        .toString().replace(",", ".")));
                    }
                }
            });

    return convertView;
}

private OnClickListener RetourArticleListener = new OnClickListener() {

    @Override
    public void onClick(View v) {
        boolean on = ((ToggleButton) v).isChecked();
        int position = (Integer) v.getTag();
        RelativeLayout rl = (RelativeLayout) v.getParent();
        if (on) {
            // Article returned
            rl.setBackgroundColor(Color.RED);
            lineList.get(position).setLineArticleReturned(true);
        } else {
            // Article used
            rl.setBackgroundColor(Color.WHITE);
            lineList.get(position).setLineArticleReturned(false);
        }

    }
};
}
Hetiwos
  • 229
  • 1
  • 4
  • 13
  • As you scroll in your listview, views that go out of sight are re-used, hence your 11th view has the same background color as the first – Frank D. Feb 12 '15 at 14:49
  • @FrankD. Is there any way I can counter this? – Hetiwos Feb 12 '15 at 14:55
  • You could make an (Array)List containing booleans (e.g. "differentColor = true") that correspond to the state of the items in your linelist. So for each position in linelist, you can check if the item should have a different background color. In your getView method, use the position to check your list of booleans and set the background color for that item accordingly – Frank D. Feb 12 '15 at 15:33

2 Answers2

2

The problem, as pointed out by Alex.F, is the recycling behavior of the ListView. The solution is pretty simple:

You Line item already has a boolean that corresponds to the color state, which we'll use.

In your RetourArticleListener, change the if-else statement to:

if (on) {
     // Article returned
     lineList.get(position).setLineArticleReturned(true);
     notifyDatasetChanged();
} else {
     // Article used
     lineList.get(position).setLineArticleReturned(false);
     notifyDatasetChanged();
}

The notifyDatasetChanged method will make sure your getView() method is called.

In your getView() method, check the value for "LineArticleReturned" and change the background color accordingly. Add this somewhere at the end of your getView method so you're sure all required variables are initialized by then. I assume you have a method called getLineArticleReturned() which returns the boolean value of LineArticleReturned in your Line class.

 if(lineList.get(position).getLineArticleReturned()==true){
     RelativeLayout rl = (RelativeLayout)          
     convertView.findViewById(R.id.name_of_your_relative_layout_here);
     rl.setBackgroundColor(Color.RED);

 }else{
     rl.setBackgroundColor(Color.WHITE);
 }
Frank D.
  • 1,306
  • 1
  • 13
  • 23
1

This is a clear case of an issue with listView's recycling mechanism. You should read up on it for example in this SO Q&A

Community
  • 1
  • 1
Alex.F
  • 5,648
  • 3
  • 39
  • 63
  • And when I say an issue I don't mean that there is something wrong with it, rather that you have to write your code accordingly :) – Alex.F Feb 12 '15 at 14:56