RecyclerView移除视图后布局管理器仍处理子视图致崩溃求助
解决RecyclerView拖拽项到外部布局时的快速操作崩溃问题
问题描述
开发底部带RecyclerView的界面,支持将列表项拖拽到上方的RelativeLayout中。正常操作功能正常,但快速拖拽放置时会触发崩溃,抛出以下异常:
java.lang.ClassCastException: android.widget.RelativeLayout$LayoutParams cannot be cast to androidx.recyclerview.widget.RecyclerView$LayoutParams
崩溃原因
核心是竞态条件:快速拖拽时,RecyclerView的布局管理器仍在处理子视图的布局/测量逻辑,但该视图已被移除并将LayoutParams替换为RelativeLayout.LayoutParams。当RecyclerView后续尝试访问该视图的LayoutParams时,触发类型转换错误。
可行解决方案
方案1:延迟拖拽启动,确保RecyclerView释放视图
修改onSwiped方法,先从RecyclerView中移除视图,再延迟启动拖拽,给RecyclerView足够时间完成布局更新:
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val view: View = viewHolder.itemView val position: Int = viewHolder.bindingAdapterPosition if (position != RecyclerView.NO_POSITION) { adapter.removeItem(position) view.visibility = View.INVISIBLE // 立即从RecyclerView中移除视图,切断引用 (view.parent as? RecyclerView)?.removeView(view) // 延迟启动拖拽,等待RecyclerView完成当前布局周期 view.postDelayed({ val shadowBuilder = MyDragShadowBuilder(view = view) view.startDragAndDrop(null, shadowBuilder, view, 0) }, 100) // 延迟时间可根据实际情况调整,50-100ms足够 } }
方案2:使用克隆视图进行拖拽(彻底避免竞态)
不直接拖拽RecyclerView的原item视图,而是创建视图副本,原视图由RecyclerView正常回收:
- 修改
onSwiped方法创建克隆视图:
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position: Int = viewHolder.bindingAdapterPosition if (position != RecyclerView.NO_POSITION) { adapter.removeItem(position) // 克隆原视图 val originalView = viewHolder.itemView as TextView val clonedView = LayoutInflater.from(originalView.context) .inflate(R.layout.item_draggable, parentLayout, false) as TextView clonedView.text = originalView.text // 启动克隆视图的拖拽 val shadowBuilder = MyDragShadowBuilder(view = clonedView) clonedView.startDragAndDrop(null, shadowBuilder, clonedView, 0) } }
- 调整拖拽放置逻辑:
DragEvent.ACTION_DROP -> { val draggedView = event.localState as? View draggedView?.let { val touchOffsetX = it.width / 2 val touchOffsetY = it.height / 2 var newX = event.x - touchOffsetX var newY = event.y - touchOffsetY // 限制视图在父布局范围内 newX = newX.coerceIn(0f, (parentLayout.width - it.width).toFloat()) newY = newY.coerceIn(0f, (parentLayout.height - it.height).toFloat()) it.x = newX it.y = newY it.layoutParams = RelativeLayout.LayoutParams(it.width, it.height) parentLayout.addView(it) } true }
方案3:强制RecyclerView刷新布局(兜底)
如果前两种方案仍有问题,在移除视图后强制RecyclerView刷新,确保它清理无效引用:
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val view: View = viewHolder.itemView val position: Int = viewHolder.bindingAdapterPosition if (position != RecyclerView.NO_POSITION) { adapter.removeItem(position) view.visibility = View.INVISIBLE (view.parent as? RecyclerView)?.removeView(view) // 强制RecyclerView刷新布局 recyclerView.post { recyclerView.requestLayout() } val shadowBuilder = MyDragShadowBuilder(view = view) view.startDragAndDrop(null, shadowBuilder, view, 0) } }
验证
修改后测试快速拖拽操作,确认崩溃不再出现,同时保证正常拖拽功能不受影响。
内容的提问来源于stack exchange,提问作者Sirop4ik
相关产品推荐
相关产品推荐

