RecyclerView正在复制项目。

13

当我滚动 RecyclerView 时,它会复制项目,而我已经调用了 adapter.notifyDataSetChanged()

所以,很可能是我在错误的地方调用了数据集更新,但我找不到原因。

这是一些代码:

       RecyclerView packageRecyclerView;
        Intent intent;
        ChecklistAdapter recyclerViewAdapter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_checklist);
    
            intent = getIntent();
            size = intent.getIntExtra("size", 0);
            Log.d(TAG, "onCreate - Qtd Questões: " + size);
    
            packageRecyclerView = findViewById(R.id.package_lst);
    
            LinearLayoutManager recyclerLayoutManager = new LinearLayoutManager(this);
            packageRecyclerView.setLayoutManager(recyclerLayoutManager);
    
            DividerItemDecoration dividerItemDecoration =
                    new DividerItemDecoration(packageRecyclerView.getContext(),
                            recyclerLayoutManager.getOrientation());
            packageRecyclerView.addItemDecoration(dividerItemDecoration);
    
            List<Checklist> modelList = new ArrayList<>();
            recyclerViewAdapter = new ChecklistAdapter(modelList,this);
            modelList = getPackages();
            recyclerViewAdapter = new ChecklistAdapter(modelList,this);
    
    //        recyclerViewAdapter = new ChecklistAdapter(getPackages(),this);
    
            packageRecyclerView.setAdapter(recyclerViewAdapter);
    
    
        }
    
        private List<Checklist> getPackages() {
            List<Checklist> modelList = new ArrayList<>();
            Log.d(TAG, "getPackages: " + size);
            for (int i=0; i<size;i++) {
    
                List<String> priceList = new ArrayList<>();
                priceList.add("Sim");
                priceList.add("Não");
                priceList.add("Não se Aplica");
                modelList.add(new Checklist(intent.getStringExtra("q"+i), priceList));
            }
    
            recyclerViewAdapter.notifyDataSetChanged();
            return modelList;
        }

这是我的适配器:

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

    private List<Checklist> packageList;
    private Context context;

    public ChecklistAdapter(List<Checklist> packageListIn
            , Context ctx) {
        packageList = packageListIn;
        context = ctx;
    }

    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
    }

    @Override
    public long getItemId(int position) {
        return super.getItemId(position);
    }

    @Override
    public ChecklistAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                                    int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.rv_checklistlines, parent, false);

        ChecklistAdapter.ViewHolder viewHolder =
                new ChecklistAdapter.ViewHolder(view);
        return viewHolder;
    }


    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        super.onBindViewHolder(holder, position, payloads);
    }

    @Override
    public void onBindViewHolder(ChecklistAdapter.ViewHolder holder,
                                 int position) {
        Checklist packageModel = packageList.get(position);
        holder.packageName.setText(packageModel.getTitle());

        int id = (position+1)*100;
        for(String price : packageModel.getQuestions()){
            RadioButton rb = new RadioButton(ChecklistAdapter.this.context);
            rb.setId(id++);
            rb.setText(price);

            holder.priceGroup.addView(rb);
        }
    }

    @Override
    public int getItemCount() {
        return packageList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView packageName;
        public RadioGroup priceGroup;

        public ViewHolder(View view) {
            super(view);
            packageName = view.findViewById(R.id.package_name);
            priceGroup = view.findViewById(R.id.price_grp);

            priceGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup radioGroup, int i) {

                    Toast.makeText(ChecklistAdapter.this.context,
                            "Radio button clicked " + radioGroup.getCheckedRadioButtonId(),
                            Toast.LENGTH_SHORT).show();
                    
                }
            });
        }
    }
}
    
在下面的图像中,您可以看到单选按钮被复制,并且在所有行中有一些额外的空白空间。我在这里和其他论坛上看到了很多关于此问题的问题,但我不清楚在哪里调用此更新数据集。

enter image description here

我不是高级Android开发人员,所以如果您能解释一下,对我来说会更容易。

编辑1

清单类:

       import java.util.List;
    
    public class Checklist {
    
        String title;
        List<String> questions;
    
        public Checklist(String title, List<String> questions) {
            this.title = title;
            this.questions = questions;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public List<String> getQuestions() {
            return questions;
        }
    
        public void setQuestions(List<String> questions) {
            this.questions = questions;
}

编辑 2

适配器代码已更新。


priceListmodelList设置为全局变量,将modelList.add(new Checklist(intent.getStringExtra("q"+i), priceList));移出for循环并尝试。 - John Joe
@JohnJoe,这样我只得到了一个问题和三个单选按钮。我希望所有的问题(图5.10、5.11、5.12等)每个问题都有3个单选按钮。 - Alan Godoi
请发布您的 checkList 代码。 - John Joe
1
@Alan,你应该在setAdapter之后使用这个方法_recyclerViewAdapter.notifyDataSetChanged(); - ॐ Rakesh Kumar
@Alan,你不需要两次调用“recyclerViewAdapter = new ChecklistAdapter(modelList,this);”。 - ॐ Rakesh Kumar
显示剩余8条评论
10个回答

14

onCreateViewHolder() 中使用 holder.setIsRecyclable(false)

对我有效。


这有助于解决ViewPager2和具有多个视图类型的回收器适配器的问题。 - Vishal Kottarathil
3
这将违反RecyclerView的实际用例。 - pandey_shubham

13

我重现了你的问题。

onBindViewHolder方法中添加holder.priceGroup.removeAllViews();会修复它。像这样:

 @Override
public void onBindViewHolder(ChecklistAdapter.ViewHolder holder,
                             int position) {
    Checklist packageModel = packageList.get(position);
    holder.packageName.setText(packageModel.getTitle());

    int id = (position+1)*100;
    holder.priceGroup.removeAllViews();

    for(String price : packageModel.getQuestions()){
        RadioButton rb = new RadioButton(ChecklistAdapter.this.context);
        rb.setId(id++);
        rb.setText(price);
        holder.priceGroup.addView(rb);
    }
}

您一直在添加视图,而没有删除之前的视图。


1
太棒了,它可以工作了。我还删除了setHasStableIds和getItemViewType,也许这可以帮助其他人。感谢大家的时间。 - Alan Godoi
1
刚刚意识到另一件事,它不再重复了,但是当我滚动时,它会丢失选定的单选按钮。 - Alan Godoi
1
我理解,实际上每次滚动视图都会被销毁,因此您需要存储这些数据,并在返回时检索它们,在返回到视图时设置它们。 - COYG
1
请在此处查看答案:https://stackoverflow.com/questions/32255720/recyclerview-adapter-resets-radio-buttons - COYG
1
非常感谢!非常感谢!非常感谢!我遇到了类似的问题,因为我将视图添加到了父级recyclerview中。在添加新数据/滚动后,数据会重复,现在我知道原因了,谢谢!!! - Irfandi D. Vendy
显示剩余4条评论

6

在向RecyclerViewAdapter插入数据之前,您只需调用list.clear();。然后,在调用adapter.notifyDatasetChange()之后再次调用它。


2
请不要分享代码的图像,而应该分享代码片段。 - Gk Mohammad Emon

4

像下面的例子一样,在onBindViewHolder()中使用holder.setIsRecyclable(false)。

@Override
    public void onBindViewHolder(UsageStatVH holder, int position) {
        holder.bindTo(list.get(position));
        holder.setIsRecyclable(false);
    }

4

在您的活动中,您需要为适配器设置setHasStableIds(true)。

在您的适配器类中,您需要设置以下方法。它可能会对您有所帮助。这个方法对我很有效。

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

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


    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
    }

您希望像这样吗?

在此输入图片描述


1
现在它正在重复所有项目,而不仅仅是收音机。 - Alan Godoi
我按照你说的做了。我想要和你图片上展示的一样,但每个问题只有3个单选按钮。用你的代码会重复。 - Alan Godoi

1
请在ChecklistAdapter中覆盖以下方法
@Override
public long getItemId(int position) {
    return position;
}

在你的 onCreate() 中添加以下内容:
recyclerViewAdapter.setHasStableIds(true);

我收到了一个错误信息:在适配器已经注册观察者的情况下,无法更改该适配器是否具有稳定的ID。 - Alan Godoi
@Alan 在调用 packageRecyclerView.setAdapter(recyclerViewAdapter); 前,你必须使用 recyclerViewAdapter.setHasStableIds(true);。 - Zain
它不再复制收音机,但它正在记录项目和收音机,因此我无法获取响应。 - Alan Godoi
请记住,您正在注释掉修改lastCheckedRadioGroup的代码块。 - Zain
@Alan,请检查我的新答案。 - COYG
显示剩余5条评论

0

这是因为您将列表设置了三次到适配器中,

目前我无法编写整个代码,但是请尝试替换您代码中的此块,它应该可以正常工作。

请尝试以下代码:

    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(packageRecyclerView.getContext(), recyclerLayoutManager.getOrientation()); 
    packageRecyclerView.addItemDecoration(dividerItemDecoration);
    List<Checklist> modelList = new ArrayList<>(); 
    modelList = getPackages(); recyclerViewAdapter = new ChecklistAdapter(modelList,this);  
    packageRecyclerView.setAdapter(recyclerViewAdapter);

同时也从你的getPackages()函数中移除下面这行代码。

recyclerViewAdapter.notifyDataSetChanged();

仍然是同样的问题,当我滚动时,项目会重复。 - Alan Godoi
你正在for循环中创建modelList,将其从for循环中移除,并仅添加一次数据。 - Prayag Gediya
modelList在for循环之前已经被创建,我只是在for循环内部向其中添加项目。 - Alan Godoi
是的,这就是问题所在,您正在循环中添加数据,因此您的列表将包含相同的数据三次。 - Prayag Gediya
看一下这张图片,在循环中我添加了一个标题(图像问题)和3个单选按钮到每个问题。当我滚动时,它会复制为六个单选按钮。 - Alan Godoi

0

不要再使用

 List<Checklist> modelList = new ArrayList<>();
    recyclerViewAdapter = new ChecklistAdapter(modelList,this);
    modelList = getPackages();
    recyclerViewAdapter = new ChecklistAdapter(modelList,this);
    packageRecyclerView.setAdapter(recyclerViewAdapter);

使用方法

    recyclerViewAdapter = new ChecklistAdapter(getPackages(),this);
    packageRecyclerView.setAdapter(recyclerViewAdapter);

并修改您的getPackages方法如下

private List<Checklist> getPackages() {
 List<Checklist> modelList = new ArrayList<>();
 Log.d(TAG, "getPackages: " + size);

for (int i=0; i<size;i++) {
   List<String> priceList = new ArrayList<>();
    priceList.add("Sim");
    priceList.add("Não");
    priceList.add("Não se Aplica");
    modelList.add(new Checklist(intent.getStringExtra("q"+i), priceList));
}
return modelList;
}

希望这能对你有所帮助。


@Alan,如果你在滚动时发现项目重复了,那么问题可能出现在你的onBindViewHolder中,当你将单选按钮添加到单选组中时。 这是因为每次滚动时,都会调用你的onBindViewHolder方法并添加新的单选按钮。 - Jakir Hossain
你能再解释一下吗?你的回答并没有解决问题。 - Alan Godoi
@Alan,我添加了另一个答案。 - Jakir Hossain

0

修改你的清单...

public class Checklist {

    String title;
    List<String> questions;
    boolean isRadioButtonAdded;

    public Checklist(String title, List<String> questions) {
        this.title = title;
        this.questions = questions;
    }
    public boolean getIsAdded(){
        return isRadioButtonAdded;
    }
    
    public void setIsAdded(boolean isAdded){
        this.isRadioButtonAdded = isAdded;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<String> getQuestions() {
        return questions;
    }

    public void setQuestions(List<String> questions) {
        this.questions = questions;
}

并修改您的onBindViewHolder

@Override
public void onBindViewHolder(ChecklistAdapter.ViewHolder holder,
                             int position) {
    Checklist packageModel = packageList.get(position);
    holder.packageName.setText(packageModel.getTitle());

    int id = (position+1)*100;
    if(!packageModel.getIsAdded){
        for(String price : packageModel.getQuestions()){
            RadioButton rb = new RadioButton(ChecklistAdapter.this.context);
            rb.setId(id++);
            rb.setText(price);

            holder.priceGroup.addView(rb);
            packageModel.setIsAdded(true)
        }
    }
}

列表仍在重复。 :( - Alan Godoi

0

如果您正在使用 DiffUtils,我在尝试从 Room DB 获取列表并更新 Recyclerview 时遇到了相同的问题,即制作重复项。

之前

private fun getLocalMessage(offset: Int, scrollToEnd: Boolean) {
    vm.getLocalMessage(channelId = channelId, offset = offset) {
        lifecycleScope.launch {
            val job = CoroutineScope(Dispatchers.IO).launch {
                channelMessageDB.addAll(it)
            }
            job.join()
            bindMessagesToRv(messageList = channelMessageDB, enableScrollToEnd = scrollToEnd)
        }
    }
}

private fun bindMessagesToRv(
    message: ChannelMessageEntity.ChannelMessageData? = null,
    messageList: List<ChannelMessageEntity.ChannelMessageData> = emptyList(),
    enableScrollToEnd: Boolean = false
) {
    lifecycleScope.launch {
        if (messageList.isNotEmpty()) {
            val job = CoroutineScope(Dispatchers.IO).launch {
                channelMessage.addAll(messageList)
            }
            job.join()
            channelMessage.sortedBy {
                it.messageTime
            }.apply {
                adapter.addItem(newItem = this)
            }

        }
        message?.let {
            channelMessage.add(it)
            adapter.addItem(newItem = channelMessage)

        }

    }
}

然后我将我的localmessage函数更新为这样
private fun getLocalMessage(offset: Int, scrollToEnd: Boolean) {
    vm.getLocalMessage(channelId = channelId, offset = offset) {
        lifecycleScope.launch {
            bindMessagesToRv(messageList = it, enableScrollToEnd = scrollToEnd)
        }
    }
}

已经有两个列表了,我把它们都传递过去了,现在只剩下一个。可能会有帮助,祝好运 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接