因为不了解 Android 事件分发机制,居然被实习生嘲笑了,天呐
可以看到 Activity 中和 View 中都没有 onInterceptTouchEvent,Android 的事件分发就需要了解这三个方法,下面一一分析。
dispatchTouchEvent
他是事件分发处理函数,如果事件传递到了当前 View,那么这个方法会被调用,返回结果表示是否消耗当前事件,false 表示事件允许继续分发,返回 true 则表示该事件不再继续分发,有可能是当前 View 的 onTouchEvent 或者是子 View 的 dispatchTouchEvent 消费了。
不用我说,当发生点击操作时,会先从 Activity 的 dispatchTouchEvent 方法开始,然后依次传递给子视图,Activity 的 dispatchTouchEvent 方法非常简单,首先判断是不是按下,如果是则调用一下 onUserInteraction(虽然这个方法什么也没做),然后 superDispatchTouchEvent 方法经过层层调用,会传递到 View 或 ViewGroup 的 dispatchTouchEvent 中。
如果 superDispatchTouchEvent 返回 true,则事件结束,表示有 View 已经消费了,false 的话会传递给自身的 onTouchEvent 方法进行消费,表示所有的 View 的 onTouchEvent 的返回了 false,没有人去处理这个事件,只能交给自己处理。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
对上面加粗地方解释一下:如果还想深入了解,就需要看 DecorView,但过程也不是很多。
在这里想象一下,在 Activity 中有个 ViewGoup,他的 dispatchTouchEvent 方法 true,当单击这个 ViewGoup 的时候,那么这里就直接结束了,如果返回 false,那么会走 Activity 的 onTouchEvent 方法。
如果返回 super.dispatchTouchEvent(ev),那么这里就麻烦了,会走 onInterceptTouchEvent 方法,如果 onInterceptTouchEvent 方法返回 true,那么就代表他要拦截当前事件,他的 onTo
uchEvent 就会被调用。如果返回 false,那么表示不拦截当前事件,会继续传递给它的子元素,子元素的 dispatchTouchEvent 方法就会被调用,然后反复这个过程直到事件被最终处理。
另外如果给这个 View 设置了 OnTouchListener,那么 OnTouchListener.onTouch 方法就会被调用,这个事件如何处理就要看 onTouch 的返回值,如果返回 true 则 onTouchEvent 方法不会被调用。在 onTouchEvent 中,如果设置了 OnClickListener,那么他的 onClick 就会被调用。
onInterceptTouchEvent
这个方法是在 dispatchTouchEvent 中调用的,用来判断是否拦截当前事件,如果当前 View 拦截了事件,那么在后续同一个事件序列中,这个方法不会被再次调用,默认返回 false,返回 true 表示拦截。
所以,在上一张图

onTouchEvent
也在 dispatchTouchEvent 中调用,用来处理点击事件,如果返回 true,则表示当前 View 消耗了此事件。
常见解决方案
ScrollView 嵌套 ScrollView
假设现在有两个 ScrollView,每个 ScrollView 都需要上下滑动,如果不解决,那就是这个样子。

只需自定义一个 ScrollView,在 onInterceptTouchEvent 下这样写,即可解决。requestDisallowInterceptTouchEvent 用于请求父 view 不要拦截 Touch 事件,也就是让父 View 不要管 onInterceptTouchEvent 方法,直接执行向子 View 分发事件的逻辑。同样 ListView 嵌套 ListView、ScrollView 嵌套 ListView 也可以使用此办法,
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.onInterceptTouchEvent(ev);
}

收起软键盘
现在 Activity 中存有一个 EditText 和一个 Button,现在要你单机按钮或者空白处的时候收起软键盘,你会怎么做?
首先明确的是,如果我们不做一些手段,点击 EditText 使软键盘弹出后在点任何其他方,软键盘是不会收回的,了解了事件分发后,就可以利用 Activity 中的 dispatchTouchEvent 处理,在其中判断如果事件是 ACTION_DOWN 时,获取当前具有焦点的 View,然后隐藏软键盘即可。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction()==MotionEvent.ACTION_DOWN){
View v = getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && v!=null) {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
评论