RecyclerView子项用MotionLayout侧滑的冲突与崩溃问题
我之前也遇到过一模一样的问题,这本质是RecyclerView的滚动触摸事件和MotionLayout侧滑事件的分发优先级冲突,再加上触摸处理逻辑的小漏洞导致了空指针崩溃。下面是经过验证的解决方案:
解决RecyclerView子项MotionLayout侧滑与滚动的手势冲突及崩溃问题
1. 优化MotionScene的侧滑配置
先调整你的MotionScene,给OnSwipe添加更精确的触摸约束,减少不必要的手势误触发:
<?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto"> <Transition motion:constraintSetStart="@layout/chat_item_start" motion:constraintSetEnd="@layout/chat_item_end" motion:duration="500"> <OnSwipe motion:targetId="@+id/background" motion:touchAnchorSide="right" motion:dragDirection="dragLeft" motion:touchRegionId="@+id/background" <!-- 限定触摸触发区域为背景 --> motion:maxVelocity="1000" <!-- 限制滑动速度,降低和滚动的冲突概率 --> motion:threshold="0.2" /> <!-- 降低触发阈值,更容易激活侧滑 --> </Transition> </MotionScene>
2. 修复触摸事件监听器的核心逻辑
你原来的触摸监听返回false会导致事件继续向上传递,让RecyclerView提前拦截滚动;而且空指针大概率是因为holder.motion为空或者触摸变量未正确初始化。修改后的逻辑如下:
private var initialX = 0f private var initialY = 0f holder.itemView.setOnTouchListener { view, motionEvent -> val motionLayout = holder.motion ?: return@setOnTouchListener false val progress = motionLayout.progress when(motionEvent.actionMasked) { // 用actionMasked处理多点触摸更安全 MotionEvent.ACTION_DOWN -> { initialX = motionEvent.x initialY = motionEvent.y // 按下时立即让MotionLayout接管事件,防止RecyclerView抢先拦截 view.parent.requestDisallowInterceptTouchEvent(true) } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { // 根据当前动画进度决定最终状态 if (progress > 0.5f) { motionLayout.transitionToEnd() } else { motionLayout.transitionToStart() } // 动画结束后释放拦截,允许RecyclerView正常滚动 view.parent.requestDisallowInterceptTouchEvent(false) } MotionEvent.ACTION_MOVE -> { val deltaX = abs(motionEvent.x - initialX) val deltaY = abs(motionEvent.y - initialY) // 动态判断手势类型:横向偏移大于纵向时保持拦截,否则释放让RecyclerView滚动 if (deltaX > deltaY && deltaX > 10) { // 增加最小偏移阈值,避免微小滑动误判 view.parent.requestDisallowInterceptTouchEvent(true) } else if (deltaY > deltaX && deltaY > 10) { view.parent.requestDisallowInterceptTouchEvent(false) } } } // 让MotionLayout自己处理侧滑逻辑,不要手动模拟 motionLayout.onTouchEvent(motionEvent) }
3. 完善TransitionListener的空安全处理
原来的Listener里没有做空安全判断,这是空指针异常的潜在来源之一,修改后:
holder.motion?.setTransitionListener(object : MotionLayout.TransitionListener { override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {} override fun onTransitionStarted(p0: MotionLayout?, startId: Int, endId: Int) { p0?.let { // 动画开始时强制拦截事件,防止滚动干扰动画执行 it.parent.requestDisallowInterceptTouchEvent(true) } } override fun onTransitionChange(p0: MotionLayout?, startId: Int, endId: Int, progress: Float) {} override fun onTransitionCompleted(p0: MotionLayout?, currentId: Int) { p0?.let { // 动画完成后释放拦截,恢复RecyclerView的滚动功能 it.parent.requestDisallowInterceptTouchEvent(false) } } })
4. 额外的崩溃预防小贴士
针对你提到的VelocityTracker空指针,还要注意:
- 确保MotionLayout是子项布局的根布局,保证触摸事件能完整传递到它
- 在Adapter的
onBindViewHolder中,务必确认holder.motion已经正确初始化,避免为null - 避免在动画过程中快速切换手势(比如侧滑到一半突然滚动),这会导致MotionLayout内部状态混乱
通过以上调整,侧滑时MotionLayout会优先接管事件,滚动时RecyclerView正常响应,同时彻底避免了空指针崩溃的风险。
内容的提问来源于stack exchange,提问作者Borombo
相关产品推荐
相关产品推荐

