Test:
设有这样一个布局:
<ScrollView
android:id="@+id/scroller"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:fillViewport="true">
...
</ScrollView>
</ScrollView>
滑动时打印两个ScrollView的scrollY值,可发现只有外层ScrollView可以滑动,内层ScrollView不能滑动;
如果是点击事件或横向滑动,只会触发内层ScrollView的Down,Move,Up事件;
如果是竖向滑动,则会出现如下情况:
innerView: DOWN
innerView: MOVE
innerView: MOVE
innerView: CANCEL
outerView: MOVE
outerView: MOVE
outerView: MOVE
.....
outerView: UP
事件从内层ScrollView转移到了外层ScrollView处理;
外部拦截:
如果想要实现接触内层ScrollView时内层滑动,接触到内层以外的部分外层滑动的效果,只需要重写外层ScrollView:
class OuterScrollView(context: Context, attrs: AttributeSet) : ScrollView(context, attrs) {
override fun onInterceptTouchEvent(ev: MotionEvent?) = false
}
即外部拦截需要重写父View的onInterceptTouchEvent
方法,对Down,Up,Move分别做是否需要拦截的判断
内部拦截:
这里需要借助另外一个方法:
ViewGroup.requestDisallowInterceptTouchEvent(true)
onInterceptTouchEvent
方法的作用是,ViewGroup分发事件之前调用这个方法判断是否需要拦截事件requestDisallowInterceptTouchEvent
方法可以使onInterceptTouchEvent
方法对Move和Up事件的拦截判断失效,即对于Move和Up事件直接分发不判断拦截;
但是requestDisallowInterceptTouchEvent
不会影响ViewGroup对Down事件拦截的判断
内部拦截:
1.外部的View不做特殊处理,但是要保证Down事件不被父View拦截
2.在内部View的dispatchTouchEvent
方法中酌情调用parent.requestDisallowInterceptTouchEvent
方法
例如:
class InnerScrollerView(context: Context, attrs: AttributeSet) : ScrollView(context, attrs) {
lateinit var parent: ScrollView
//记录上一个事件的Y值,用来结合当前事件的Y值判断滑动方向
private var lastY = 0f
//记录Down事件发生的坐标,为了判断是否是水平滑动
private var downX = 0f
private var downY = 0f
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
//水平滑动不做处理
if (ev!!.action == MotionEvent.ACTION_DOWN) {
downX = ev.x
downY = ev.y
} else {
if (abs(ev.x - downX) > abs(ev.y - downY)) {
return super.dispatchTouchEvent(ev)
}
}
//处理竖直方向的滑动
//如果外部ScrollView滑到底部,且滑动方向向下,就不允许外部ScrollView拦截Move事件
if (parent.scrollY + parent.height == parent.getChildAt(0).bottom && ev.y < lastY) {
parent.requestDisallowInterceptTouchEvent(true)
} else if (scrollY == 0 && ev.y > lastY) {
//如果内部ScrollView滑倒顶部,且滑动方向向上,就允许外部ScrollView拦截Move事件向上滑动
parent.requestDisallowInterceptTouchEvent(false)
} else {
parent.requestDisallowInterceptTouchEvent(ev.y >= lastY)
}
lastY = if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_MOVE) ev.y else 0f
return super.dispatchTouchEvent(ev)
}
}