NestedScrollView嵌套MotionLayout卡顿无响应兼容问题
MotionLayout嵌套NestedScrollView滑动冲突解决方案
问题根因
动效卡顿、无响应本质是触摸事件拦截冲突:NestedScrollView作为可滑动父容器,默认会优先接管触摸事件流,导致内部MotionLayout的OnSwipe手势监听拿不到完整的触摸事件序列,自然无法正常触发动效。
之前尝试的android:descendantFocusability="blocksDescendants"属性仅用于处理焦点抢占问题(例如页面打开后滚动容器自动滚到获焦子View位置这类场景),和触摸事件分发无关,因此配置后不会生效。
分步修复方案
1. 先修正基础布局测量问题
当前外层NestedScrollView的高度配置为wrap_content,在ConstraintLayout中会出现测量异常,连带影响内部MotionLayout的手势判定,先修改外层布局的NestedScrollView配置:
<androidx.core.widget.NestedScrollView android:id="@+id/nested_scroll" android:layout_width="match_parent" android:layout_height="0dp" android:fillViewport="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"> <!-- 内部LinearLayout和子View保持原有配置即可 --> </androidx.core.widget.NestedScrollView>
- 高度改为
0dp配合上下约束,让NestedScrollView正确占满父布局可用高度,避免无意义的全内容高度测量 - 增加
android:fillViewport="true",保证内容高度不足时NestedScrollView也能撑满可视区域,避免MotionLayout高度测量错误
2. 升级依赖+修正MotionScene配置
首先将ConstraintLayout依赖升级到2.1.0以上稳定版,旧版本MotionLayout本身存在嵌套滑动兼容bug:
// app模块build.gradle中配置 implementation "androidx.constraintlayout:constraintlayout:2.1.4"
之后修改MotionScene中的OnSwipe节点,增加手势区域和嵌套滑动兼容配置:
<OnSwipe motion:dragDirection="dragRight" motion:dragThreshold="15" motion:maxAcceleration="10" motion:onTouchUp="decelerateAndComplete" motion:touchAnchorId="@id/button" motion:touchAnchorSide="right" motion:touchRegionId="@id/button" motion:nestedScrollFlags="disableScroll" />
配置说明:
motion:touchRegionId="@id/button":明确指定只有触摸在滑块按钮区域时,MotionLayout才响应滑动手势,其余区域的滑动手势完全交给NestedScrollView处理,避免手势判定冲突motion:nestedScrollFlags="disableScroll":当MotionLayout响应滑块滑动时,临时禁用父容器NestedScrollView的滚动拦截,保证手势事件序列完整传递给MotionLayout
3. 兜底兼容方案(上述配置不生效时使用)
如果修改配置后仍存在手势冲突,自定义NestedScrollView重写事件拦截逻辑,触摸到滑块区域时主动放行事件,不做拦截:
class TouchCompatibleScrollView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : NestedScrollView(context, attrs) { // 配置需要放行触摸事件的子View列表 var passThroughTargetViews: List<View> = emptyList() override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { if (ev.actionMasked == MotionEvent.ACTION_DOWN) { val touchX = ev.rawX val touchY = ev.rawY passThroughTargetViews.forEach { target -> val location = IntArray(2) target.getLocationOnScreen(location) val left = location[0] val top = location[1] val right = left + target.measuredWidth val bottom = top + target.measuredHeight // 触摸点落在目标View范围内时,不拦截事件 if (touchX in left..right && touchY in top..bottom) { return false } } } return super.onInterceptTouchEvent(ev) } }
替换布局中原有的NestedScrollView为该自定义控件,在页面代码中传入需要放行事件的滑块按钮即可:
val scrollView = findViewById<TouchCompatibleScrollView>(R.id.nested_scroll) val sliderView = findViewById<FakeSliderView>(R.id.accessSlider) // 等布局加载完成后获取内部滑块实例 sliderView.post { val motionLayout = sliderView.findViewById<MotionLayout>(R.id.motionLayout) val slideBtn = motionLayout.findViewById<View>(R.id.button) scrollView.passThroughTargetViews = listOf(slideBtn) }
运行效果参考

内容的提问来源于stack exchange,提问作者Byron
相关产品推荐
相关产品推荐

