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

来源:互联网 发布:linux 解压gz文件脚本 编辑:程序博客网 时间:2024/06/04 19:14
这是小王君熬夜写的博客,虽然很晚,还是坚持写下去吧!(注:本文是以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
  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.     if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
  3.         onUserInteraction();  
  4.     }  
  5.     if (getWindow().superDispatchTouchEvent(ev)) {  
  6.         return true;  
  7.      }  
  8.      return onTouchEvent(ev);  
  9. }  
分析:事件触摸是封装在MotionEvent中进行传递;
Activity转发的时候,先交给Activity所附属的Window,于是调用PhoneWindow的superDispatchTouchEvent()方法。

接下来,我们进入PhoneWindow类里进行查阅如下:
注:类PhoneWindow继承了Window ,Window只有这一个子类;   PhoneWindow.class在framework源码中。
[java] view plain copy
  1. @Override  
  2.  public boolean superDispatchTouchEvent(MotionEvent event) {  
  3.       return mDecor.superDispatchTouchEvent(event);  
  4.   }  
分析:mDecor 就是DecorView,PhoneWindow将事件传递交给DecorView处理。

接下来,我们进入DecorView类进行查阅如下:
[java] view plain copy
  1. private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {  
  2.      public boolean superDispatchTouchEvent(MotionEvent event) {  
  3.         return super.dispatchTouchEvent(event);  
  4.      }  
  5. }  
分析:DecorView将事件处理交给了super爸爸,这个爸爸就是FrameLayout,爸爸继承爷爷ViewGroup,实际就是ViewGroup的dispatchTouchEvent()。至于DecorView如何将事件转发到xml中布局的根view,这里不作分析,反正肯定是可以转发过来的,因为我们写的xml布局是能接受触摸事件的。这里的根view,就是setContentView中最外层的view,一般都是一个组控件ViewGroup。

接下来,我们要开始查阅ViewGroup的源码,如下:
这里主要看注释的核心代码,能梳理清楚事件转发的流程即可,不必理解每一行代码
[java] view plain copy
  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {..  
  2.                   
  3.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  4.     //...code  表示此位置代码有省略,这里只展示核心代码  
  5.   
  6.         if (actionMasked == MotionEvent.ACTION_DOWN  
  7.                 || mFirstTouchTarget != null) {  
  8.             final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  9.             if (!disallowIntercept) {  
  10.                 //Viewgroup拦截事件  
  11.                 intercepted = onInterceptTouchEvent(ev);  
  12.                 ev.setAction(action); // restore action in case it was changed  
  13.             } else {  
  14.                 intercepted = false;  
  15.             }  
  16.         } else {  
  17.             // There are no touch targets and this action is not an initial down  
  18.             // so this view group continues to intercept touches.  
  19.             intercepted = true;  
  20.         }  
  21.           
  22.         //...code   
  23.           
  24.         //intercepted为false,Viewgroup不拦截时,事件转发到子View的代码执行如下:  
  25.         if (!canceled && !intercepted) {  
  26.             //...code  
  27.               
  28.             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {  
  29.                 // Child wants to receive touch within its bounds.  
  30.                 mLastTouchDownTime = ev.getDownTime();  
  31.                 mLastTouchDownIndex = childIndex;  
  32.                 mLastTouchDownX = ev.getX();  
  33.                 mLastTouchDownY = ev.getY();  
  34.                 //若子View处理点击事件,会给下面两个变量赋值  
  35.                 newTouchTarget = addTouchTarget(child, idBitsToAssign);  
  36.                 alreadyDispatchedToNewTouchTarget = true;  
  37.                 break;  
  38.             }  
  39.             //...code  
  40.               
  41.         }  
  42.           
  43.         //...code  
  44.   
  45.         //ViewGroup拦截下事件或子View的onTouchEvent(..)返回false时,点击事件交给ViewGroup处理  
  46.         if (mFirstTouchTarget == null) {  
  47.             // No touch targets so treat this as an ordinary view.  
  48.             //ViewGroup处理点击事件  
  49.             handled = dispatchTransformedTouchEvent(ev, canceled, null,  
  50.                     TouchTarget.ALL_POINTER_IDS);  
  51.         }   
  52.       
  53.     //...code  
  54.       
  55.     }  
  56.               
  57.     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,  
  58.             View child, int desiredPointerIdBits) {  
  59.         //...code  
  60.           
  61.         //child为空时,ViewGroup处理点击事件  
  62.         if (child == null) {  
  63.             handled = super.dispatchTouchEvent(event);  
  64.         } else {  
  65.             handled = child.dispatchTouchEvent(event);  
  66.         }  
  67.           
  68.         //...code  
  69.           
  70.     }  
  71. }  
分析:super.dispatchTouchEvent(event)调用爸爸的方法

接下来,我们继续查阅View.class
[java] view plain copy
  1. public boolean dispatchTouchEvent(MotionEvent event) {  
  2.         if (mInputEventConsistencyVerifier != null) {  
  3.             mInputEventConsistencyVerifier.onTouchEvent(event, 0);  
  4.         }  
  5.   
  6.         if (onFilterTouchEventForSecurity(event)) {  
  7.             //noinspection SimplifiableIfStatement  
  8.             ListenerInfo li = mListenerInfo;  
  9.             if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
  10.                     && li.mOnTouchListener.onTouch(this, event)) {  
  11.                 return true;  
  12.             }  
  13.   
  14.             if (onTouchEvent(event)) {  
  15.                 return true;  
  16.             }  
  17.         }  
  18.   
  19.         if (mInputEventConsistencyVerifier != null) {  
  20.             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  
  21.         }  
  22.         return false;  
  23. }  
分析: 
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。

到这里,源码分析就结束了。我们想进步,还是需要读者自己去翻开源码查阅,才能更深刻理解事件分发机制。
辛苦大家能看到这里,文章有不够准确的地方,期待大家能在下面评论中指出,一起学习进步!!!       
———小王君       (*^__^*) 

原创粉丝点击