RecyclerView ambiguos setVisibility函数,点击一个视图影响多个视图

RecyclerView ambiguos setVisibility function, clicking on one view affects multiple views

This is the project 我正在尝试 运行。这是我来自 RecyclerView.Adapter class

的 onBindViewHolder 代码
@Override
    public void onBindViewHolder(ViewHolder holder, final int position) {

        TextView title = (TextView) holder.view.findViewById(R.id.title);
        final TextView desc = (TextView) holder.view.findViewById(R.id.desc);
        final ImageView imageView = (ImageView) holder.view.findViewById(R.id.imageView);

        title.setText(pojos.get(position).getTitle());
        desc.setText(pojos.get(position).getDesc());

        imageView.setImageResource(pojos.get(position).getImage());

        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                desc.setText("clicked");
                desc.setBackgroundColor(Color.BLUE);
                imageView.setImageResource(R.drawable.heart_red);
            }
        });

    }

列表加载正常,问题发生在调用 imageView 的 onclicklistener 时。

desc.setText("clicked");

上面的行对它被点击的列表项进行了更改。但是

 desc.setBackgroundColor(Color.BLUE);

执行此行时,更改会反映在列表中的多个项目中。出了什么问题?在下面显示的图片中,我单击了项目 0,文本变为 "clicked" 并设置了颜色。但是当我向下滚动时,第 12 项也受到我点击第 0 项的影响。只有背景颜色变化得到反映,而不是文本变化。我该如何阻止它?

我一直在努力解决这个问题,如果我的问题不清楚,请下载项目并尝试执行代码以理解我的意思。

我遇到了类似的问题(多个列表元素的数字发生了变化,而不仅仅是一个)。我认为这是因为回收视图的工作原理,我能够通过将我计划更改的所有内容设置为我想要的默认值来修复它。

IE:如果您想将背景更改为蓝色,请在加载列表时将那些不应为蓝色的背景设置为灰色(或任何您希望的默认设置)。

所以在这里:

ViewHolder vh = new ViewHolder(v);
return vh; 

您想指定默认值

在这里尝试使用这个适配器:

public class myAdapter extends RecyclerView.Adapter<CopyOfConversationAdapter.ViewHolder> {
private ArrayList<conversationItem> pojos;
// inner class to hold a reference to each item of RecyclerView 
public static class ViewHolder extends RecyclerView.ViewHolder {

    TextView title;
    TextView desc; 
    ImageView imageView;  


    public ViewHolder(View itemLayoutView) {
        super(itemLayoutView);
        title= (TextView) itemLayoutView.findViewById(R.id.title);
        desc=  (TextView) itemLayoutView.findViewById(R.id.desc);
        imageView=  (ImageView) itemLayoutView.findViewById(R.id.imageView);
    }
}

// Return the size of your itemsData (invoked by the layout manager)
@Override
public int getItemCount() {
    return pojos.size();
}

public CopyOfConversationAdapter(Pojos[] pojos) {
    this.pojos = new ArrayList<conversationItem>();
    this.pojos.addAll(Arrays.asList(Items));
}
// Create new views (invoked by the layout manager)
@Override
public CopyOfConversationAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // create a new view
    View itemLayoutView;
        itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.comments_item_layout_, null);

    ViewHolder viewHolder = new ViewHolder(itemLayoutView);
    return viewHolder;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {

    // - get data from your itemsData at this position
    // - replace the contents of the view with that itemsData

    viewHolder.title.setText(pojos.get(position).getSender());
    viewHolder.desc.setText(pojos.get(position).getSnippet());
    viewHolder.imageView.setText(pojos.get(position).getIcon());
    viewHolder.imageView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
             desc.setText("clicked");
                desc.setBackgroundColor(Color.BLUE);
                imageView.setImageResource(R.drawable.heart_red);
        }
    });

}

}

发生这种情况是因为视图得到回收和重用。

因此,当视图被回收时,如果您不再更改它们,它会保留 "old" 视图的属性。因此,当您向下滚动到数字 12 时,用于保存数字 1 的视图将被回收(因为它不再在屏幕上可见),并用于创建数字 12。这就是蓝色显示在数字上的原因12.

例如,当项目被单击时,您需要将 "clicked" 值保存到您的 POJO 对象中。然后在绘制项目时,检查该值并根据该值设置正确的图像/背景颜色。

我已经在下面的代码中完成了这个,所以它应该让您大致了解该怎么做:

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    TextView title = (TextView) holder.view.findViewById(R.id.title);
    final TextView desc = (TextView) holder.view.findViewById(R.id.desc);
    final ImageView imageView = (ImageView) holder.view.findViewById(R.id.imageView);

    final MyPojo pojo = pojos.get(position);

    title.setText(pojo.getTitle());
    if(!pojo.clicked) {
        desc.setText(pojo.getDesc());
        imageView.setImageResource(pojo.getImage());
        desc.setBackgroundColor(Color.argb(0,0,0,0));
    } else {
        desc.setText("clicked");
        desc.setBackgroundColor(Color.BLUE);
        imageView.setImageResource(R.drawable.heart_red);
    }

    imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            pojo.clicked = true;
            desc.setText("clicked");
            desc.setBackgroundColor(Color.BLUE);
            imageView.setImageResource(R.drawable.heart_red);
        }
    });
}

并且我在 MyPojo class 中添加了一个 "clicked" 布尔值。

public class MyPojo {

    String title;
    String desc;
    int image;
    boolean clicked;
 }

看来您对通过在 onBindViewHolder 中调用 findViewById 来使用 RecyclerView 感到困惑。这些昂贵的查找应该发生在 onCreateViewHolder 中,您可以在其中查找所有视图并将它们的引用保存到您的自定义视图持有者。我继续查看 github 存储库中的代码并提出以下更改:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

private ArrayList<MyPojo> pojos;

// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public TextView title;
    public TextView desc;
    public ImageView imageView;

    public ViewHolder(View v) {
        super(v);

        // all expensive findViewById lookups happen in ViewHolder constructor,
        // which is called only when onCreateViewHolder is called
        this.title = (TextView) v.findViewById(R.id.title);
        this.desc = (TextView) v.findViewById(R.id.desc);
        this.imageView = (ImageView) v.findViewById(R.id.imageView);
    }
}

// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<MyPojo> pojos) {
    this.pojos = pojos;
}

// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.row, parent, false);
    // set the view's size, margins, paddings and layout parameters
    ViewHolder vh = new ViewHolder(v);
    return vh;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    // this callback will be constantly called during scrolling
    // therefore, to make it smooth, we should not make any expensive operations here
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    holder.title.setText(pojos.get(position).getTitle());
    holder.desc.setText(pojos.get(position).getDesc());
    holder.imageView.setImageResource(pojos.get(position).getImage());

    // you'll need to implement this function based on the way you decide to save clicked state for each clicked view
    if(isClickedState(position)) {
          holder.imageView.setImageResource(R.drawable.heart_red);
    } else {
          // provide some default background
          holder.imageView.setImageResource(R.drawable.default);
    }

    holder.imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // you'll need to implement this function to save clicked position
            saveClickForPosition(position)
            imageView.setImageResource(R.drawable.heart_red);
        }
    });
}

// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
    return pojos.size();
}
}

这应该是调试的起点,因为遵循此模式将保证正确回收。

在另一个答案中也提到,您确实需要记住每个项目的点击状态,将此状态保存在 MyPojo 对象或其他地方应该相对容易实现。

只需在您的适配器中添加一个方法 class 在 getItemCount 方法

之后
@Override
    public int getItemViewType(int position) {
        return position;
    }

它将解决问题

如果你知道项目的数量并且它是固定的,你可以使用

setItemViewCacheSize( numItems)

这将解决问题,因为它会在该编号之后开始重复使用项目,但是,您将失去回收视图的所有好处。