ScrollView 继承的是 FrameLayout ,它的高的 mode 是 UNSPECIFIED 。
有个原则是:如果mode 是 UNSPECIFIED 但是值写为 match_parent ,那么 其实效果就相当于 wrap_content 。
如果上面传过来的 mode 是 UNSPECIFIED ,但是在自己自定义View 的时候又想改掉,那就可以使用 MeasureSpec.makeMeasureSpec:
1 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ |
自定义 View 的时候如果需要响应 Margin 的话,需要重写 generateParams 方法,重写的是哪个呢?是传入 AttributeSet 参数的那个,因为我们在 xml 解析的时候需要用到的是这个。
View 的 dispatchTouchEvent 只有事件处理,ViewGroup 里面会有事件分发。
在ViewRootImpl 中会创建 WindowInputEventReceiver 用于事件的接收,这里面有 onInputEvent 回调方法。
在接收到Event 的时候,会交给 DecorView 去 dispatchTouchEvent ,这里面会通过 mWindow.getCallback ,记住,这个callback 是 Activity,这样,事件就到了 Activity 层面:
1 |
|
所以,整体流程是: ViewRootImpl -> DecorView -> Activity
事件的处理
Event 的处理只要搞清楚以下几个问题:
onTouchEvent 在哪里执行
onTouch 和 onClick 执行的位置,关系
longClick
在 View 上按下,然后手指移动到 View 之外,然后抬起手指,为什么 onClick 不执行?
在 DOWN 事件中,会判断长按事件。在 UP 里面,会判断 onClick 事件。
onTouch 如果执行人会 true ,后续就不会执行 onClick 了,因为这里面会涉及“与”操作的短路行为:! result && onTouchEvent 这种操作,意味着,如果 result 是 true 的话,后续的 onTouchEvent 就不会执行了,而 onClick 在 onTouchEvent 里面
1 | //View.java dispatchTouchEvent |
可以看出来,当判断用户有做 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 三个方法,查看里面为什么会有冲突。