【问题标题】:OnClickListener in ArrayAdapter is taking action on wrong rowsArrayAdapter 中的 OnClickListener 正在对错误的行执行操作
【发布时间】:2014-02-16 05:32:56
【问题描述】:

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

但是,当用户单击第 1 行上的按钮时,第 3 行的按钮实际上被选中。我不确定为什么会这样。下面是来自我的getView 方法与 cmets 的相关代码的 sn-p。

我的切换按钮布局

    <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)而不是在setTag中设置int,而是设置holder并在onClickListener中使用holder

更新

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

【问题讨论】:

  • 发生这种情况 bcoz listview 回收视图
  • 我读到...有没有办法解决这个问题?
  • 如果有帮助,请检查stackoverflow.com/questions/20611123/…
  • 很好的解释。但是,我注意到在您提供的示例中,您正在更改列表中的值,然后调用 notify。但是,我相信我的情况有点不同,因为我正在更改与数据列表无关的列表项中按钮的属性。所以调用 notify 是没有意义的。
  • 我发帖是为了暗示它是如何工作的。由你来修改。

标签: java android android-listview android-arrayadapter


【解决方案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>

行.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>

【讨论】:

    【解决方案2】:

    你的问题是列表视图回收视图

    您必须为列表视图的每一行存储切换按钮的状态。 例如,创建存储每行信息的类,假设 ColorInfo 包含颜色和 isChecked 布尔值。 所以而不是

    Color c = colorsList.get(position);
    

    会的

    ColorInfo colorInfo = colorsList.get(position);
    

    在getview中

    togglebutton.setCheck(colorInfo.isCheck)
    

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

    【讨论】:

    • hmmm 所以我应该在我的 Colo POJO 中添加一个像 isCheck 这样的布尔属性?
    • 是的,如前所述,您需要保留一行的状态,以便 listview 在绘制时可以为每一行提供状态
    • 你能看到我更新的代码吗?我在我的 POJO 中保留状态,然后通过调用 notifyDatasetChanged 相应地更新列表。我还是有同样的问题...
    【解决方案3】:

    您正在为 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);
      }
    

    【讨论】:

    • hmmm 现在我根本看不到按钮切换,即使日志消息显示在 if else
    • 对不起ItemView 是一个错字。应该说c.XXX。我已经修好了
    • 但这仍然是错误的。 c.XXX 应该是 rowView.XXX
    • 非常感谢您的帮助。我已经实施并纠正了我们讨论的所有内容,并更新了问题中的代码。但是,我仍然遇到同样的问题。如果我遗漏了什么,请您再看一下。
    • 您的语句colorList.get(i) == (Integer)v.getTag() 正在比较两个 Integer 对象以查看它们是否具有相同的内存地址。您需要改用.equals()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-10
    • 2023-01-31
    • 1970-01-01
    • 2018-02-02
    • 1970-01-01
    • 2015-01-06
    相关资源
    最近更新 更多