0%

事件分发机制

ScrollView 继承的是 FrameLayout ,它的高的 mode 是 UNSPECIFIED 。

有个原则是:如果mode 是 UNSPECIFIED 但是值写为 match_parent ,那么 其实效果就相当于 wrap_content 。

如果上面传过来的 mode 是 UNSPECIFIED ,但是在自己自定义View 的时候又想改掉,那就可以使用 MeasureSpec.makeMeasureSpec:

1
2
3
4
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMode, MeasureSpec.Exactly);
}

自定义 View 的时候如果需要响应 Margin 的话,需要重写 generateParams 方法,重写的是哪个呢?是传入 AttributeSet 参数的那个,因为我们在 xml 解析的时候需要用到的是这个。

View 的 dispatchTouchEvent 只有事件处理,ViewGroup 里面会有事件分发。

在ViewRootImpl 中会创建 WindowInputEventReceiver 用于事件的接收,这里面有 onInputEvent 回调方法。

在接收到Event 的时候,会交给 DecorView 去 dispatchTouchEvent ,这里面会通过 mWindow.getCallback ,记住,这个callback 是 Activity,这样,事件就到了 Activity 层面:

1
2
3
4
5
6
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

所以,整体流程是: ViewRootImpl -> DecorView -> Activity

事件的处理

Event 的处理只要搞清楚以下几个问题:

  1. onTouchEvent 在哪里执行

  2. onTouch 和 onClick 执行的位置,关系

  3. longClick

  4. 在 View 上按下,然后手指移动到 View 之外,然后抬起手指,为什么 onClick 不执行?

在 DOWN 事件中,会判断长按事件。在 UP 里面,会判断 onClick 事件。

onTouch 如果执行人会 true ,后续就不会执行 onClick 了,因为这里面会涉及“与”操作的短路行为:! result && onTouchEvent 这种操作,意味着,如果 result 是 true 的话,后续的 onTouchEvent 就不会执行了,而 onClick 在 onTouchEvent 里面

1
2
3
4
5
6
7
8
9
10
11
12
13
//View.java  dispatchTouchEvent


9932 ListenerInfo li = mListenerInfo;
9933 if (li != null && li.mOnTouchListener != null
9934 && (mViewFlags & ENABLED_MASK) == ENABLED
9935 && li.mOnTouchListener.onTouch(this, event)) {
9936 result = true;
9937 }
9938
9939 if (!result && onTouchEvent(event)) {
9940 result = true;
9941 }

可以看出来,当判断用户有做 setOnTouchListener 以及其他的一些条件之后,才会做: li.mOnTouchListener.onTouch(this, event) ,也就是调用 onTouch 方法,并且如果 onTouch 返回true ,则 if (!result && onTouchEvent(event)) 就会短路,后续的 onTouchEvent 就不会执行。

当收到move 的时候,判断区域是不是在这个 View 里面,如果不在,就将一系列的 callback 给 remove 掉,press 设置为 false ,后续的onclick 也就不触发。

按压 500ms(29以后改成 400ms ) 内松手了,长按就取消了。

响应多个手指,第一个手指按下去是 ACTION_DOWN,接着其他手指是 ACTION_POINT_DOWN 了

当ViewGroup 分发事件的时候,会根据 Z 值对子 View 进行排序,如果大家都不设置 z 值,则默认为 0 ,按照默认顺序。可以用 FrameLayout 来体会一下,如果所有的 child 叠加的话,最后添加的那个 child 在最上面,也是优先响应事件的。

ViewGroup 的 dispatch 在 DOWN 事件中确定了目标处理的 child ,如果某个 child 在 DOWN 的时候返回 true ,那么在就会 target.child 就会指向这个 child 。

上下滑动,把事件给 ListView,当左右滑动给 ViewPager 处理。处理冲突:

  • 内部拦截,在 dispatchTouchEvent 的 ACTION_DOWN 的时候,getParent().requestDisallowInterceptTouchEvent(true); 来实现,并且父容器里面 onInterceptTouchEvent 的时候,要判断 ACTION_DOWN事件不能拦截 !

为什么父容器能够将事件从子View 中抢过来?

子 View 需要出让的时候, requestDisallowInterceptTouchEvent(false) ,置为 false 了。

第一个 MOVE 事件的时候,判断子 View 是响应了 DOWN事件的,这时候就会给子 View 发送过去 Cancel 事件。

因为 MOVE 事件会触发多次,第二次 intercept 就会变为 true 了,就能自己处理这个 MOVE 行为。

  • 外部拦截

处理事件冲突,就看看 onInterceptTouchEvent 、dispatchTouchEvent、onTouchEvent 三个方法,查看里面为什么会有冲突。

谢谢你的鼓励