嵌套RecyclerView的TouchEvent分发异常,请求解决方案
解决嵌套RecyclerView的触摸事件分发与滚动冲突问题
问题背景
你需要实现嵌套RecyclerView的三类交互逻辑,但当前自定义布局的触摸拦截逻辑存在问题:
- 点击ParentRecyclerView子项,背景变灰(Parent获取触摸事件)
- 点击ChildRecyclerView子项,触发对应Parent子项背景变灰(Parent获取触摸事件)
- 横向拖动ChildRecyclerView时,Child可正常滚动(Child获取触摸事件,Parent不拦截)
当前问题:自定义TouchThroughConstraintLayout在ACTION_DOWN阶段就因isViewInBounds返回true而拦截触摸事件,导致ChildRecyclerView无法获取横向滚动的事件。
问题原因
在Android触摸事件分发机制中,onInterceptTouchEvent一旦在ACTION_DOWN返回true,后续所有触摸事件都会直接交给当前View处理,子View将完全失去事件处理权。你的初始逻辑在ACTION_DOWN阶段就判断点击位置在Child内就拦截,直接剥夺了Child处理滚动事件的机会。
解决方案
核心思路是延迟拦截判断,不在ACTION_DOWN阶段决定拦截,而是在ACTION_MOVE阶段根据滑动方向动态调整拦截策略,同时补充Child点击事件的回调逻辑。
修改后的自定义布局代码
class TouchThroughConstraintLayout(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) { private var mDownX = 0f private var mDownY = 0f // 滑动阈值,可根据实际需求调整 private val SCROLL_THRESHOLD = 10f private lateinit var childViewGroup: ViewGroup // Child子项点击回调,用于通知Parent处理选中逻辑 var onChildItemClickListener: (() -> Unit)? = null fun setChildView(childViewGroup: ViewGroup) { this.childViewGroup = childViewGroup // 给ChildRecyclerView绑定触摸监听,捕获点击事件 if (childViewGroup is RecyclerView) { childViewGroup.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() { override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { if (e.action == MotionEvent.ACTION_UP) { val child = rv.findChildViewUnder(e.x, e.y) if (child != null) { onChildItemClickListener?.invoke() } } return false } }) } } override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { mDownX = ev.x mDownY = ev.y // ACTION_DOWN不拦截,让事件先传递给Child return false } MotionEvent.ACTION_MOVE -> { val deltaX = abs(ev.x - mDownX) val deltaY = abs(ev.y - mDownY) // 判断是否为横向滚动:X方向位移大于Y方向,且超过阈值 return if (deltaX > SCROLL_THRESHOLD && deltaX > deltaY) { // 横向滚动,不拦截,交给Child处理 false } else { // 点击或纵向操作,拦截事件,交给Parent处理 true } } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { // 重置触摸坐标 mDownX = 0f mDownY = 0f return false } } return super.onInterceptTouchEvent(ev) } }
使用示例(ParentRecyclerView的Adapter)
class ParentAdapter(private val dataList: List<ParentData>) : RecyclerView.Adapter<ParentAdapter.ParentViewHolder>() { private var selectedPosition = -1 inner class ParentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val touchLayout: TouchThroughConstraintLayout = itemView.findViewById(R.id.touch_layout) val childRecyclerView: RecyclerView = itemView.findViewById(R.id.child_rv) } override fun onBindViewHolder(holder: ParentViewHolder, position: Int) { // 绑定ChildRecyclerView数据... holder.childRecyclerView.adapter = ChildAdapter(dataList[position].childData) // 绑定自定义布局的ChildView与点击回调 holder.touchLayout.setChildView(holder.childRecyclerView) holder.touchLayout.onChildItemClickListener = { // 触发Parent项的选中逻辑 notifyItemChanged(selectedPosition) selectedPosition = position notifyItemChanged(position) } // Parent子项自身的点击逻辑 holder.itemView.setOnClickListener { notifyItemChanged(selectedPosition) selectedPosition = position notifyItemChanged(position) } // 设置选中状态的背景 holder.itemView.setBackgroundResource( if (selectedPosition == position) R.drawable.bg_selected else R.drawable.bg_normal ) } // 其他Adapter方法... }
关键修改说明
- 延迟拦截策略:ACTION_DOWN阶段不拦截事件,保证ChildRecyclerView能收到初始触摸事件,为后续滚动处理保留机会
- 滑动方向判断:在ACTION_MOVE阶段通过计算X/Y方向的位移差,动态决定是否拦截事件——横向滚动时放行,点击/纵向操作时拦截给Parent
- Child点击回调:通过给ChildRecyclerView添加触摸监听,捕获子项点击事件并回调给Parent,实现点击Child子项时Parent对应项背景变灰的需求
内容的提问来源于stack exchange,提问作者CodingBruceLee
相关产品推荐
相关产品推荐

