PART 1 dispatchTouchEvent, onTouchEvent和onInterceptTouchEvent
三行概括:
onInterceptTouchEvent
是ViewGroup
的方法(因为View
已经是叶子结点,不能再选择是否intercept了,只能选择是否consume),onTouchEvent
是View
的方法。它们都能拦截Touch事件。
onInterceptTouchEvent
的return true
代表消费事件,不向下传递给子View
。
onTouchEvent
的return true
代表传递完毕,向上传递到ViewGroup/Activity
的onTouchEvent
。
1.1
为了解决ViewDragLayout与ScrollView的滚动冲突,或者说想要制作一个无法滚动的ScrollView
,去了解了一下这三个回调函数的执行顺序。
dispatchTouchEvent
onTouchEvent
onInterceptTouchEvent
1.2
假如有这样一个页面,包含一个Activity
,一个MyLayout (extends framelayout)
, 一个MyView (extends TextView)
。

-
Activity
里覆写:- dispatchTouchEvent()
- onTouchEvent()
-
MyLayout
里覆写:- dispatchTouchEvent()
- onInterceptTouchEvent()
- onTouchEvent()
-
MyView
里覆写:- dispatchTouchEvent()
- onTouchEvent()
并且每个方法都打印这样格式的内容:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.d("TAG", "Activity dispatchTouchEvent DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d("TAG", "Activity dispatchTouchEvent DOWN");
break;
//other cases..
}
boolean b = super.dispatchTouchEvent(ev);
Log.d("TAG", "Activity dispatchTouchEvent RETURNS " + b);
return b;
}
那么 点击一下MyView
,会打印出:
- Activity dispatchTouchEvent DOWN
先执行了Activity的dispatchTouchEvent。
然后从switch中break,执行super.dispatchTouchEvent(ev);,控制权被分发到了MyLayout
。 - MyLayout dispatchTouchEvent DOWN
然后再次执行MyLayout覆写的dispatchTouchEvent中的super.dispatchTouchEvent(ev);,
分发到MyLayout的onInterceptTouchEvent。 - MyLayout onInterceptTouchEvent DOWN
- MyLayout onInterceptTouchEvent RETURNS false
这时候ViewGroup知道它对Intercept Down Event不感兴趣。所以它会把事件分发给所有的children,这里只有一个child就是MyView。 - 6..7..见下图:

是一个类似递归(栈)的操作,先进去别的函数再回来再继续执行原来的函数。
1.3
我遇到的问题是自己定义的ViewDragLayout
(里面组合了ViewDragHelper
)在它的子View的交界处无法通过ViewDragHelper回调的tryCaptureView
捕获子View(ScrollView),导致ScrollView
获取了焦点,也就是说在触摸边界的时候,ViewDragLayout
的onInterceptTouchEvent
本应返回true的,实际却返回了false,导致ViewDragLayout
对滑动不感兴趣,ScrollView
就处理了这次滑动。验证了一下确实是这样:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean b = mViewDragHelper.shouldInterceptTouchEvent(ev);
System.out.println("---->" + b);
return b;
}


注意只要看最后一个打印的是true或者false就行了,上面这些打印的内容是一瞬间打印出的,持续的滑动并不会持续触发onInterceptTouchEvent。
下面有几个方案:
- 在
onInterceptTouchEvent
里返回:return MotionEvent.ACTION_MOVE == ev.getAction();
会先打印false再打印true,false是DOWN操作,开始MOVE之后就true了。这样看起来没问题,我想实现的就是让ScrollView失去焦点嘛,但有两个问题:
- 我发现即便是轻轻点一下,ACTION_MOVE也会先false再true,说明MOVE操作包含了DOWN和滑动,这样会导致点击ScrollView里的内容都没有响应了。
-
ScrollView
里面还有Banner,这么做会让Banner无法滑动。
-
在
onInterceptTouchEvent
里返回:return MotionEvent.ACTION_DOWN == ev.getAction();
一直打印true。这样就更不行了。 -
if (mScroll.getScrollY() > 0) return true;
本意是当ScrollView的一旦开始滚动就让他失去焦点,然而发现只有在撒手的时候getScrollY才会刷新,不行。 -
计算touch的位置相对于边界处的高度,如果高度差<一定的像素就return true。比较困难,因为ScrollView的边界一直在变。
-
继承
ScrollView
,覆写onInterceptTouchEvent
(或者onTouchEvent
)让它始终返回false (可以想象,ScrollView本身的onInterceptTouchEvent一定是在滑动起来之后就让onInterceptTouchEvent返回true了)。
这方法很Easy也能达到效果,这样一来ScrollView就丧失了滑动的效果,因为onInterceptTouchEvent返回false代表不消费这次操作,把操作递交给子View去处理,而子View是LinearLayout不能滑动,所以就没法处理,而据我猜测LinearyLayout和RelativeLayout这种基本的Layout一定是直接返回false的,所以他们里面的ViewPager之类的东西是始终可以获取到Touch事件的。采用。
PART 2 事件分发机制
之前看书的时候,作者举了个伪代码的例子,还是蛮形象的:

从上面的图2.1里面可以得出,事件分发步骤是:
- 判断当前ViewGroup的onInterceptTouchEvent是否拦截事件。
- 如果1里面onInterceptTouchEvent拦截了事件,那调用onTouchEvent,判断当前ViewGroup是否消费事件(这一步会先检查onTouchListener的onTouch函数的返回值,具体见下面的图2.2)。
- 如果1里面onInterceptTouchEvent没有拦截事件,那么调用child.dispatchTouchEvent去dfs判断(注意到这个函数是递归函数)。
也就是说,onInterceptTouchEvent决定的是,是否在当前ViewGroup就执行onTouchEvent。如果是false,它的child才有机会继续递归地执行dispatch;如果是true,那当前ViewGroup自己就执行onTouchEvent了,不再向下传递。
递归(DFS)!
再回头看看上面的执行顺序,霍然开朗了:
叶子结点的onTouchEvent返回了之后,才一层层向上继续执行父ViewGroup的onTouchEvent、dispatchTouchEvent。
优先级
onTouchListener优先级高于onTouchEvent:

//onTouchListener的优先级比onTouchEvent要高
public boolean dispatchTouchEvent(MotionEvent event) {
......
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
......
return result;
}
--
ref:
http://www.cnblogs.com/gyzboy/p/4518188.html
http://gundumw100.iteye.com/blog/1052270
《Android开发艺术探索》p141
网友评论