Android RecyclerView滚动时子项CheckBox、EditText值自动清空问题
RecyclerView滚动时子项CheckBox/EditText状态丢失解决方案
问题根源
RecyclerView自带View复用机制:滚动时离开可视区域的ItemView会被回收,复用到新进入屏幕的子项上。当前实现存在两个核心问题:
- 绑定子项时强制将CheckBox重置为未选中状态,没有读取历史存储的状态
- 没有将用户操作产生的勾选状态、输入内容持久化到绑定的数据源中,复用时自然无法还原状态
实现步骤
1. 修改数据实体类,增加状态存储字段
所有控件状态必须存储在和子项绑定的数据源对象中,不能依赖View本身存状态。先修改Child实体类,新增两个状态字段:
public class Child { // --- 原有业务字段保留 --- private String answerName; private int answerId; // --- 新增状态存储字段 --- private boolean isChecked = false; // 存储CheckBox勾选状态,默认未选中 private String percentageInput = ""; // 存储EditText输入内容,默认为空 // 原有getter/setter省略,补充新增字段的访问方法 public boolean isChecked() { return isChecked; } public void setChecked(boolean checked) { isChecked = checked; } public String getPercentageInput() { return percentageInput; } public void setPercentageInput(String percentageInput) { this.percentageInput = percentageInput; } }
如果分组头部的edHeaderPercentage输入框也需要保留状态,给SectionHeader类也加对应的字符串字段存储输入值即可。
2. 修改Adapter子项绑定逻辑
核心原则:所有控件状态从数据源读取绑定,用户操作时立刻将状态回写到对应数据源对象。注意绑定前必须先移除旧的监听器,避免View复用时监听器错乱。
修改onBindChildViewHolder方法实现如下:
@Override public void onBindChildViewHolder(ChildViewHolder childViewHolder, int sectionPos, int childPos, Child child) { // 第一步:先清空控件原有监听器,避免绑定阶段触发错误回调 childViewHolder.chk_answer.setOnCheckedChangeListener(null); // 移除EditText绑定的旧TextWatcher if (childViewHolder.etPercentage.getTag() instanceof TextWatcher) { childViewHolder.etPercentage.removeTextChangedListener((TextWatcher) childViewHolder.etPercentage.getTag()); } // 第二步:从数据源读取值绑定控件,禁止硬编码重置状态 childViewHolder.chk_answer.setText(child.getAnswerName()); childViewHolder.tv_surveyAnswerId.setText(String.valueOf(child.getAnswerId())); // 读取存储的勾选状态,替换原有硬编码的setChecked(false) childViewHolder.chk_answer.setChecked(child.isChecked()); // 读取存储的输入内容 childViewHolder.etPercentage.setText(child.getPercentageInput()); childViewHolder.etPercentage.setImeOptions(EditorInfo.IME_ACTION_DONE); // 第三步:设置监听器,用户操作时实时同步状态到数据源 // CheckBox选中状态监听 childViewHolder.chk_answer.setOnCheckedChangeListener((btn, isChecked) -> { child.setChecked(isChecked); // 原有业务回调保留 if (listener != null) { listener.onItemClick(child, isChecked); } }); // EditText输入监听 TextWatcher inputWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void afterTextChanged(Editable s) { String inputContent = s.toString().trim(); child.setPercentageInput(inputContent); // 原有业务回调保留 if (listener != null) { listener.onTextPercentageChange(String.valueOf(child.getAnswerId()), inputContent); } } }; // 将Watcher存在Tag中,下次复用时方便移除 childViewHolder.etPercentage.setTag(inputWatcher); childViewHolder.etPercentage.addTextChangedListener(inputWatcher); }
3. 分组头部输入框状态保留(可选)
如果需要保留分组头部edHeaderPercentage的输入内容,用完全相同的逻辑处理onBindSectionViewHolder即可:
- 给
SectionHeader类加字段存储输入值 - 绑定前先移除旧的TextWatcher
- 从数据源读取值设置给EditText
- 加TextWatcher实时回写输入内容到
SectionHeader对象
注意事项
- 禁止在
onBindXXXViewHolder方法中硬编码重置控件状态(比如原有代码的chk_answer.setChecked(false)),所有控件展示值必须以数据源中存储的内容为准 - 给可输入、可选中的控件设置监听器前,必须先移除旧的监听器,否则View复用时会触发多个监听器回调,导致内容串位、数据错乱
- 如果后续做下拉刷新、列表数据更新操作,需要做新旧数据的状态映射,不要直接全量替换数据源导致已存储的状态被清空
实现效果参考:
内容的提问来源于stack exchange,提问作者Enamul Haque
相关产品推荐
相关产品推荐

