这是小王君熬夜写的博客,虽然很晚,还是坚持写下去吧!(注:本文是以ViewGroup的点击事件的处理为例来分析)
一,在分析源码前,我们先总结下事件转发机制的一些简单结论:
1,当用户开始触摸activity时,Activity调用diaptcherTouchEvent开始转发,会一层层的向下进行转发,也就是每个View都会调用diaptcherTouchEvent方法。这里需要注意的是,ViewGroup在将事件转发给子view的时候,根据子view添加进ViewGroup时间的逆序排列转发事件,这是android框架的内部机制实现的。
2,当事件分发到最后一个子View的时,会以从下往上的顺序调用onTouchEvent方法。如果哪个view对这个事件感兴趣,就处理该事件,执行onTouchEvent里面的代码,如果感兴趣了还返回true,那么事件就被消费了,被吃了就木有啦,上层view的onTouchEvent就不会再接收事件了;否则一直将事件传递给上层的view的onTouchEvent,直到最后Activity的onTouchEvent收场处理。
3,在结论1中,如果在事件转发的中间,某一个view对事件感兴趣中途进行拦截,会调用该view的onInterceptTouchEvent方法,若该方法返回true,就会调用该view的onTouchEvent方法对事件进行处理,就不会再往下转发事件了,否则会一直转发下去。
这里,我们画一个流程图给大家看,如下:
Activity(diaptcherTouchEvent)→ViewGroup(diaptcherTouchEvent) →........→ 子View(diaptcherTouchEvent)
↓ 拦截,onInterceptTouchEvent
Activity(onTouchEvent) ← ViewGroup(onTouchEvent)
←........← 子View(onTouchEvent)
源码分析入口,首先进入Activity类查阅如下:
Activity.class中:
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- onUserInteraction();
- }
- if (getWindow().superDispatchTouchEvent(ev)) {
- return true;
- }
- return onTouchEvent(ev);
- }
分析:事件触摸是封装在MotionEvent中进行传递;
Activity转发的时候,先交给Activity所附属的Window,于是调用PhoneWindow的superDispatchTouchEvent()方法。
接下来,我们进入PhoneWindow类里进行查阅如下:
注:类PhoneWindow继承了Window ,Window只有这一个子类; PhoneWindow.class在framework源码中。
- @Override
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return mDecor.superDispatchTouchEvent(event);
- }
接下来,我们进入DecorView类进行查阅如下:
- private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return super.dispatchTouchEvent(event);
- }
- }
接下来,我们要开始查阅ViewGroup的源码,如下:
这里主要看注释的核心代码,能梳理清楚事件转发的流程即可,不必理解每一行代码
- public abstract class ViewGroup extends View implements ViewParent, ViewManager {..
- public boolean dispatchTouchEvent(MotionEvent ev) {
- //...code 表示此位置代码有省略,这里只展示核心代码
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (!disallowIntercept) {
- //Viewgroup拦截事件
- intercepted = onInterceptTouchEvent(ev);
- ev.setAction(action); // restore action in case it was changed
- } else {
- intercepted = false;
- }
- } else {
- // There are no touch targets and this action is not an initial down
- // so this view group continues to intercept touches.
- intercepted = true;
- }
- //...code
- //intercepted为false,Viewgroup不拦截时,事件转发到子View的代码执行如下:
- if (!canceled && !intercepted) {
- //...code
- if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
- // Child wants to receive touch within its bounds.
- mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = childIndex;
- mLastTouchDownX = ev.getX();
- mLastTouchDownY = ev.getY();
- //若子View处理点击事件,会给下面两个变量赋值
- newTouchTarget = addTouchTarget(child, idBitsToAssign);
- alreadyDispatchedToNewTouchTarget = true;
- break;
- }
- //...code
- }
- //...code
- //ViewGroup拦截下事件或子View的onTouchEvent(..)返回false时,点击事件交给ViewGroup处理
- if (mFirstTouchTarget == null) {
- // No touch targets so treat this as an ordinary view.
- //ViewGroup处理点击事件
- handled = dispatchTransformedTouchEvent(ev, canceled, null,
- TouchTarget.ALL_POINTER_IDS);
- }
- //...code
- }
- private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
- View child, int desiredPointerIdBits) {
- //...code
- //child为空时,ViewGroup处理点击事件
- if (child == null) {
- handled = super.dispatchTouchEvent(event);
- } else {
- handled = child.dispatchTouchEvent(event);
- }
- //...code
- }
- }
分析:super.dispatchTouchEvent(event)调用爸爸的方法
接下来,我们继续查阅View.class
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onTouchEvent(event, 0);
- }
- if (onFilterTouchEventForSecurity(event)) {
- //noinspection SimplifiableIfStatement
- ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
- && li.mOnTouchListener.onTouch(this, event)) {
- return true;
- }
- if (onTouchEvent(event)) {
- return true;
- }
- }
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
- }
- return false;
- }
li != null && li.mOnTouchListener != null为防止空指针,代码健壮性更强;
主要看后面两个:
一 ,(mViewFlags & ENABLED_MASK) == ENABLED 为true时指控件可clickable。
二 ,li.mOnTouchListener.onTouch(this, event) 回调接口的方法(由用户调用view.setOnTouchListener(..)实例化监听器接口);
当onTouch(..)返回true(四个条件都为true时),在方法onTouch(..)中处理点击事件。
当onTouch返回false时(或者四个条件任一为false),执行该View自己的onTouchEvent(event)处理该点击事件。
当onTouch返回false时(或者四个条件任一为false),执行该View自己的onTouchEvent(event)处理该点击事件。
如果事件处理了,不用给Activity的onTouchEvent()收场处理,否则由Activity的onTouchEvent()收场处理事件。
总结一下转发流程经过的窗口和view:
Acticity——>PhoneWindow——>DecorView——>—>根view—>..........—>最后一个子view
注:根view,也就是setContentView(R.layout.XX)中的布局
下图是用Eclipse的Hierarchy View查看某一页面的布局结构,其实每一个布局结构的根都是PhoneWindow$DecorView。
到这里,源码分析就结束了。我们想进步,还是需要读者自己去翻开源码查阅,才能更深刻理解事件分发机制。
辛苦大家能看到这里,文章有不够准确的地方,期待大家能在下面评论中指出,一起学习进步!!!
———小王君 (*^__^*)