您的位置:首页 > 移动开发 > Android开发

Android事件转发机制—源码分析(一)

2017-05-25 15:03 369 查看
这是小王君熬夜写的博客,虽然很晚,还是坚持写下去吧!(注:本文是以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中:

[java] view
plain copy

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源码中。

[java] view
plain copy

@Override  

 public boolean superDispatchTouchEvent(MotionEvent event) {  

      return mDecor.superDispatchTouchEvent(event);  

  }  

分析:mDecor 就是DecorView,PhoneWindow将事件传递交给DecorView处理。

接下来,我们进入DecorView类进行查阅如下:

[java] view
plain copy

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {  

     public boolean superDispatchTouchEvent(MotionEvent event) {  

        return super.dispatchTouchEvent(event);  

     }  

}  

分析:DecorView将事件处理交给了super爸爸,这个爸爸就是FrameLayout,爸爸继承爷爷ViewGroup,实际就是ViewGroup的dispatchTouchEvent()。至于DecorView如何将事件转发到xml中布局的根view,这里不作分析,反正肯定是可以转发过来的,因为我们写的xml布局是能接受触摸事件的。这里的根view,就是setContentView中最外层的view,一般都是一个组控件ViewGroup。

接下来,我们要开始查阅ViewGroup的源码,如下:
这里主要看注释的核心代码,能梳理清楚事件转发的流程即可,不必理解每一行代码

[java] view
plain copy

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

[java] view
plain copy

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)处理该点击事件。

如果事件处理了,不用给Activity的onTouchEvent()收场处理,否则由Activity的onTouchEvent()收场处理事件。

总结一下转发流程经过的窗口和view:
Acticity——>PhoneWindow——>DecorView——>—>根view—>..........—>最后一个子view

注:根view,也就是setContentView(R.layout.XX)中的布局

下图是用Eclipse的Hierarchy View查看某一页面的布局结构,其实每一个布局结构的根都是PhoneWindow$DecorView。



到这里,源码分析就结束了。我们想进步,还是需要读者自己去翻开源码查阅,才能更深刻理解事件分发机制。
辛苦大家能看到这里,文章有不够准确的地方,期待大家能在下面评论中指出,一起学习进步!!!       
———小王君       (*^__^*) 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: