ArrayAdapter中的OnClickListener正在对错误的行执行操作

时间:2014-02-16 05:32:57

标签: java android android-listview android-arrayadapter

我有一个ArrayAdapter用于列表视图,其中包含多个按钮。对于一个切换按钮,我希望基于条件具有默认状态,并且还允许用户切换按钮。

但是,当用户单击第1行上的按钮时,实际上会选择第3行的按钮。我不确定为什么会这样。以下是我的getView方法中包含评论的相关代码片段。

我的切换按钮的布局

    <ToggleButton android:id="@+id/color_toggle"
        android:layout_width="50px"
        android:layout_height="50px"
        android:focusable="false"
        android:textOn="" android:textOff="" android:layout_alignParentLeft="true"
        android:layout_marginRight="10dp"
        />

class Color {
   int id;
   int something;
}
List<Color> colorsList;

class ColorHolder {
   TextView colorNameText;
   ToggleButton toggleButton;
}


public View getView(final int position, final View convertView, final ViewGroup parent) {
  View rowView = convertView;
  Color c = colorsList.get(position);
  if (null == rowView) {
      rowView = this.inflater.inflate(R.layout.list_item_color, parent, false);
      holder = new ColorHolder();
      holder.colorNameText = (TextView) rowView.findViewById(R.id.color_name);
      holder.toggleButton = (ToggleButton) rowView.findViewById(R.id.color_toggle);


      rowView.setTag(holder);
  }
  else { 
      holder = (ColorHolder)rowView.getTag();
  }
  holder.toggleButton.setTag(c.getId());
  final ColorHolder thisRowHolder = holder;
  holder.toggleButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (thisRowHolder.toggleButton.isChecked()) {
            thisRowHolder.toggleButton.setBackgroundDrawable(//normal button);
            thisRowHolder.toggleButton.setChecked(false);
            for (int i = 0; i < colorList.size(); i++) {
               if (colorList.get(i) == (Integer)v.getTag()) {
                   colorList.get(i).setSomething(0);
                   break;
               }
            }
            adapter.notifyDataSetChanged();
        }
        else {
            thisRowHolder.toggleButton.setBackgroundDrawable(//enabled button);
            thisRowHolder.toggleButton.setChecked(true);
            for (int i = 0; i < colorList.size(); i++) {
               if (colorList.get(i) == (Integer)v.getTag()) {
                   colorList.get(i).setSomething(1);
                   break;
               }
            }
            adapter.notifyDataSetChanged();
        }
    }
});

if (c.getSomething()>0) {
   holder.toggleButton.setBackgroundDrawable(//enabled button);
   holder.toggleButton.setChecked(true);
}
else {
   holder.toggleButton.setBackgroundDrawable(//normal button);
   holder.toggleButton.setChecked(false);
}

return rowView;
}

问题

我做错了什么?为什么即使我在第一行切换按钮,第三行中的其他按钮也会切换。

我读到这是因为listView回收,是否有办法修复它?基于类似的问题,我尝试了一些无用的策略:1)将onClickListener放在if子句中。 2)而不是在int中设置setTag而不是设置holder并在holder中使用onClickListener

更新

我已根据收到的建议更新了问题中的所有代码。

3 个答案:

答案 0 :(得分:1)

希望这有助于。

活动代码

public class DemoActivity extends Activity {
    /** Called when the activity is first created. */

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ColorInfo[] clr= new ColorInfo[20];

        for(int i=0;i<20;i++){
            clr[i] = new ColorInfo();
        }

        ((ListView)findViewById(R.id.list)).setAdapter(new MyAdapter(this, 0, clr));

    }

    private static class MyAdapter extends ArrayAdapter<ColorInfo> implements OnClickListener{

        LayoutInflater inflater;
        public MyAdapter(Context context, int textViewResourceId,
                ColorInfo[] objects) {
            super(context, textViewResourceId, objects);
            inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

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

            ViewHolder holder;

            if(convertView == null){
                convertView = inflater.inflate(R.layout.row, null);
                holder = new ViewHolder();
                holder.tgl = (ToggleButton) convertView.findViewById(R.id.toggle);
                convertView.setTag(holder);
            }
            holder = (ViewHolder) convertView.getTag();
            holder.tgl.setTag(position);
            holder.tgl.setOnClickListener(this);
            holder.tgl.setChecked(getItem(position).isChecked);
            return convertView;
        }


        private static class ViewHolder{
            ToggleButton tgl;
        }


        public void onClick(View v) {

            int pos = (Integer) v.getTag();

            ColorInfo cinfo = getItem(pos);

            cinfo.isChecked = !cinfo.isChecked;

        }
    }

    private static class ColorInfo{
        boolean isChecked=false;
    }

}

main.xml中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
    </ListView>

</LinearLayout>

row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

<ToggleButton android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/toggle"
    />
</LinearLayout>

答案 1 :(得分:0)

您正在为ViewHolder使用成员变量,而不是最终的本地变量。因此,您的OnClickListener引用了最新的holder实例,它将与最近创建或回收的列表项相对应。

请改为:

  //Lock in this reference for the OnClickListener
  final ColorHolder thisRowHolder = holder; 

  holder.favButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (thisRowHolder.toggleButton.isChecked()) {
            thisRowHolder.toggleButton.setBackgroundDrawable(getResources().getDrawable(...);
            thisRowHolder.toggleButton.setChecked(false);
        }
        else {
            thisRowHolder.toggleButton.setBackgroundDrawable(getResources().getDrawable(...));
            thisRowHolder.toggleButton.setChecked(true);
        }
    }
});

...

修改

也注意到这一点。在这两行中:

holder.colorNameText = (TextView) itemView.findViewById(R.id.color_name);
holder.toggleButton = (ToggleButton) itemView.findViewById(R.id.color_toggle);

您正在某个成员变量itemView中查找视图,但您需要在rowView中找到它们,以便获取此特定行的实例。所有视图持有者都在查看同一个ToggleButton实例,它甚至可能不在屏幕上。

编辑2:

你还缺少一件事。您需要存储切换按钮的状态并重新应用它们。因此,在OnClickListener中,当您调用setChecked()时,还必须更新colorsList中的支持数据。看起来你已经在ToggleButton的ID中缓存了对正确列表元素的引用,所以应该很容易。然后将这段代码移出if / else块,然后将其放入,因此切换按钮始终更新为最新数据:

if (c.getSomething()>0) {
     holder.toggleButton.setBackgroundDrawable(getResource().getDrawable(...)));
     holder.setChecked(false);
  }
  else {
     holder.toggleButton.setBackgroundDrawable(getResource().getDrawable(...)));
     holder.setChecked(true);
  }

答案 2 :(得分:0)

您的问题是listview回收视图

您必须为listview的每一行存储切换按钮的状态。 Eg.Create类存储有关每行的信息,假设ColorInfo包含color和isChecked boolean。 所以而不是

Color c = colorsList.get(position);

它将是

ColorInfo colorInfo = colorsList.get(position);

和getview

togglebutton.setCheck(colorInfo.isCheck)

在切换按钮的onClick监听器中,您将该位置的ColorInfo对象的状态更改为toggleChecked true或false以及notifyDatasetChanged,这将解决您的问题。