RecyclerView已存储状态仍丢失Checkbox选中状态的问题排查
嗨,这个问题我太熟悉了——本质是RecyclerView的ViewHolder复用机制在搞鬼!你现在的代码里,当ViewHolder被复用时,旧的OnCheckedChangeListener还绑定在Checkbox上,导致你在onBindViewHolder里设置checked状态时,会意外触发监听逻辑,进而错误修改数据项的状态,最终出现滚动时状态乱跳的情况。
下面是具体的修复步骤和优化建议:
1. 绑定数据时先移除监听,避免触发错误逻辑
在onBindViewHolder中,设置Checkbox的选中状态之前,先暂时移除所有Checkbox的监听,等状态设置完成后再重新绑定监听。这样就能避免设置checked时触发旧的监听,导致数据被错误修改。
修改后的onBindViewHolder代码:
@Override public void onBindViewHolder(@NonNull BettingViewHolder holder, int position) { BettingItem item = items.get(position); holder.homeTextView.setText(item.homeTeam); holder.awayTextView.setText(item.awayTeam); holder.dateTextView.setText(item.date); holder.leagueTextView.setText(item.league); holder.sportsTextView.setText(item.sport); holder.homeOddsTextView.setText(Double.toString(item.homeOdds)); holder.drawOddsTextView.setText(Double.toString(item.drawOdds)); holder.awayOddsTextView.setText(Double.toString(item.awayOdds)); // 先移除所有Checkbox的监听,避免设置状态时触发旧逻辑 holder.homeCheckBox.setOnCheckedChangeListener(null); holder.drawCheckBox.setOnCheckedChangeListener(null); holder.awayCheckBox.setOnCheckedChangeListener(null); // 然后设置正确的选中状态 switch (item.outcome) { case HOME: holder.homeCheckBox.setChecked(true); holder.drawCheckBox.setChecked(false); holder.awayCheckBox.setChecked(false); break; case DRAW: holder.homeCheckBox.setChecked(false); holder.drawCheckBox.setChecked(true); holder.awayCheckBox.setChecked(false); break; case AWAY: holder.homeCheckBox.setChecked(false); holder.drawCheckBox.setChecked(false); holder.awayCheckBox.setChecked(true); break; default: holder.homeCheckBox.setChecked(false); holder.drawCheckBox.setChecked(false); holder.awayCheckBox.setChecked(false); break; } // 重新绑定监听,同时把当前item赋值给ViewHolder holder.item = item; setCheckBoxListeners(holder); } // 抽取出设置监听的方法,复用代码 private void setCheckBoxListeners(BettingViewHolder holder) { holder.homeCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { if (holder.item == null) return; if (isChecked) { holder.item.outcome = BettingItem.Outcome.HOME; holder.drawCheckBox.setChecked(false); holder.awayCheckBox.setChecked(false); } else { if (holder.item.outcome == BettingItem.Outcome.HOME) { holder.item.outcome = BettingItem.Outcome.NONE; } } listener.onEventSelected(holder.item); }); holder.drawCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { if (holder.item == null) return; if (isChecked) { holder.item.outcome = BettingItem.Outcome.DRAW; holder.homeCheckBox.setChecked(false); holder.awayCheckBox.setChecked(false); } else { if (holder.item.outcome == BettingItem.Outcome.DRAW) { holder.item.outcome = BettingItem.Outcome.NONE; } } listener.onEventSelected(holder.item); }); holder.awayCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { if (holder.item == null) return; if (isChecked) { holder.item.outcome = BettingItem.Outcome.AWAY; holder.homeCheckBox.setChecked(false); holder.drawCheckBox.setChecked(false); } else { if (holder.item.outcome == BettingItem.Outcome.AWAY) { holder.item.outcome = BettingItem.Outcome.NONE; } } listener.onEventSelected(holder.item); }); }
2. 优化监听内的冗余代码
你原来的监听里写了homeCheckBox.setChecked(isChecked);这种代码,这完全没必要——因为onCheckedChanged回调已经是在状态改变后触发的,再手动设置一次反而可能导致循环触发,直接删掉这行就行。
3. 直接使用枚举常量,避免字符串解析错误
原来的代码里用BettingItem.Outcome.valueOf("HOME")这种方式获取枚举值,不仅效率低,还可能因为字符串拼写错误抛出异常。直接用枚举常量BettingItem.Outcome.HOME就好,安全又高效。
为什么这样修改能解决问题?
当RecyclerView滚动时,ViewHolder会被复用给新的列表项。如果不先移除监听,你在onBindViewHolder里设置setChecked(true/false)时,会触发之前绑定的监听,导致旧的item数据被错误修改,最终出现状态乱跳的现象。先移除监听再设置状态,就能彻底避免这个问题,之后再重新绑定当前item的监听,逻辑就完全正确了。
内容的提问来源于stack exchange,提问作者user1660595

