RecyclerView用ConstraintSet展开错误项问题求助
解决RecyclerView中ConstraintSet展开折叠时的项复用冲突问题
你的问题我太熟悉了——刚用RecyclerView做展开折叠功能时,几乎都会踩这个ViewHolder复用的坑!咱们一步步拆解问题根源,再给你修改后的代码:
问题核心原因
- 全局状态共享导致复用混乱:你用了Adapter级别的
expanded和mainLayout变量,RecyclerView的ViewHolder是会被复用的(比如滚动后,第一项的ViewHolder会被拿来显示第10项),所有列表项共享同一个状态,点击第一项时改变的是全局状态,等第10项被复用的时候就会错误应用这个状态,所以看起来是第10项展开了。 - 布局缺少唯一ID:
mainLayout.getId()为-1是因为根ConstraintLayout没有设置唯一ID,ConstraintSet需要通过ID来匹配控件和约束,没有ID的话,复用的时候无法准确识别哪个布局该应用约束,直接导致约束错位。 - ConstraintSet重复初始化:你在
onCreateViewHolder里每次都clone ConstraintSet,这既浪费性能,也没有必要——因为折叠和展开的布局是固定的,只需要初始化一次就行。
解决方案步骤
1. 给每个列表项单独存储展开状态
要么在你的ProgressData类里加一个isExpanded字段(推荐),要么用SparseBooleanArray来记录每个位置的状态(不想修改数据类时用)。
比如修改ProgressData:
public class ProgressData { private String number; // 新增展开状态字段 private boolean isExpanded; // 构造方法、原有getter/setter public boolean isExpanded() { return isExpanded; } public void setExpanded(boolean expanded) { isExpanded = expanded; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } }
2. 给Item根布局设置唯一ID
打开你的progress_item.xml,给根ConstraintLayout加上ID:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item_root_layout" <!-- 必须加这个唯一ID --> android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- 你的其他控件 --> </androidx.constraintlayout.widget.ConstraintLayout>
3. 修改Adapter代码,绑定每个项的独立状态
把全局的状态变量去掉,将ConstraintSet初始化移到构造方法,点击监听器绑定到ViewHolder的布局,并使用当前项的状态:
public class ProgressAdapter extends RecyclerView.Adapter<ProgressAdapter.MyViewHolder> { private List<ProgressData> progressData; private ConstraintSet collapsedConstraintSet; private ConstraintSet expandedConstraintSet; private LayoutInflater inflater; public class MyViewHolder extends RecyclerView.ViewHolder { public TextView t1; public ConstraintLayout rootLayout; // 根布局移到ViewHolder里 public MyViewHolder(View itemView) { super(itemView); t1 = itemView.findViewById(R.id.t1text); rootLayout = itemView.findViewById(R.id.item_root_layout); // 用刚才设置的ID } } public ProgressAdapter(List<ProgressData> progressData, Context context) { this.progressData = progressData; inflater = LayoutInflater.from(context); // 只初始化一次ConstraintSet,提升性能 View collapsedView = inflater.inflate(R.layout.progress_item, null); collapsedConstraintSet = new ConstraintSet(); collapsedConstraintSet.clone(collapsedView); expandedConstraintSet = new ConstraintSet(); expandedConstraintSet.clone(context, R.layout.progress_item_expanded); } @NonNull @Override public ProgressAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View progressView = inflater.inflate(R.layout.progress_item, parent, false); return new MyViewHolder(progressView); } @Override public void onBindViewHolder(@NonNull ProgressAdapter.MyViewHolder holder, final int position) { ProgressData progress = progressData.get(position); holder.t1.setText(progress.getNumber()); // 先根据当前项的状态应用对应的约束 if (progress.isExpanded()) { expandedConstraintSet.applyTo(holder.rootLayout); } else { collapsedConstraintSet.applyTo(holder.rootLayout); } // 设置点击监听器,绑定当前项的状态 holder.rootLayout.setOnClickListener(v -> { // 切换当前项的展开状态 boolean newExpandedState = !progress.isExpanded(); progress.setExpanded(newExpandedState); // 执行过渡动画 TransitionManager.beginDelayedTransition(holder.rootLayout); // 应用新状态的约束 if (newExpandedState) { expandedConstraintSet.applyTo(holder.rootLayout); } else { collapsedConstraintSet.applyTo(holder.rootLayout); } // 只刷新当前项,避免全局刷新 notifyItemChanged(position); }); } @Override public int getItemCount() { return progressData.size(); } }
额外说明
- 如果不想修改
ProgressData类,可以用SparseBooleanArray来替代数据类里的isExpanded字段:
在private SparseBooleanArray expandedStates = new SparseBooleanArray();onBindViewHolder里获取状态:
点击时更新状态:boolean isExpanded = expandedStates.get(position, false);boolean newState = !expandedStates.get(position, false); expandedStates.put(position, newState); - 用
notifyItemChanged(position)而不是notifyDataSetChanged(),这样只会刷新当前点击的项,性能更好,也不会影响其他项的状态。
内容的提问来源于stack exchange,提问作者IntersectGlasses
相关产品推荐
相关产品推荐

