You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套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方法...
}

关键修改说明

  1. 延迟拦截策略:ACTION_DOWN阶段不拦截事件,保证ChildRecyclerView能收到初始触摸事件,为后续滚动处理保留机会
  2. 滑动方向判断:在ACTION_MOVE阶段通过计算X/Y方向的位移差,动态决定是否拦截事件——横向滚动时放行,点击/纵向操作时拦截给Parent
  3. Child点击回调:通过给ChildRecyclerView添加触摸监听,捕获子项点击事件并回调给Parent,实现点击Child子项时Parent对应项背景变灰的需求

内容的提问来源于stack exchange,提问作者CodingBruceLee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:53:32