安卓的事件分发的总结

来源:互联网 发布:中宏数据库 讲座 编辑:程序博客网 时间:2024/06/06 02:02

    安卓的事件分发一直是一个比较难懂单又比较重要的点,由于上一篇博客涉及到了touch事件和click事件,所以一直想写一篇自己对事件分发的认知,在写博客的时候阅读了不少的资料,其中有郭霖的博客,还有任玉刚出版的《Android开发艺术探索》,当然自己也查阅不少的源码。真的很想说感谢安卓的开源,一起分享更好的成长。

    在想弄清楚事件的分发之前必须搞清楚下面简单的概念:

     点击事件或者touch事件产生时,它的传递过程是:Activity-->Window-->DecorView(也就是常说的ViewGroup)-->view。

    为什么是这样传递的???先不急,待会分析源码。

   1)public boolean dispatchTouchEvent(MotionEvent ev):是用来进行事件分发的,只要你触摸了任何控件,就一定会调用该控件所在布局的dispatchTouchEvent方法。
                                                                                                            返回值: false 则事件往下传递。

                                                                                                            返回值: true 则消耗事件,不再传递下去。

  2)public boolean onTouchEvent(MotionEvent event): 处理点击事件,在dispatchTouchEvent方法中执行。

                                                                                                            返回值: false 则在该事件的系列中无法再次收到相关的事件。

                                                                                                            返回值: true 则消耗事件。

 3) public boolean onInterceptTouchEvent(MotionEvent ev):是否拦截事件,只有viewGroup中才有。

                                                                                                            返回值: false 则不拦截事件。

                                                                                                            返回值: true 则拦截事件,事件不再往下分发。

 4)public void requestDisallowInterceptTouchEvent(boolean disallowIntercept):要求父控件不拦截事件,该方法存在子view中。

 大家知道dispatchTouchEvent一定会执行,那么现在先分析事件的传递过程:

     先看Activity的dispatchTouchEvent方法:


可以看出Activity的dispatchTouchEvent分发给window了,那么再看window的dispatchTouchEvent:


   抽象方法???瞬间懵逼。。。。


   是不是瞬间感觉活过来了,好的接着找PhoneWindow:

而这里面的mDecor正是我们getWindow()。getDecorView()的顶级的view。

 那现在就清晰的知道事件的传递过程了Activity-->Window-->DecorView(也就是常说的ViewGroup)。

因此重点的来分析下dispatchTouchEvent的源码了:

public boolean dispatchTouchEvent(MotionEvent ev) {      final int action = ev.getAction();      final float xf = ev.getX();      final float yf = ev.getY();      final float scrolledXFloat = xf + mScrollX;      final float scrolledYFloat = yf + mScrollY;      final Rect frame = mTempRect;      boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;      if (action == MotionEvent.ACTION_DOWN) {          if (mMotionTarget != null) {              mMotionTarget = null;          }          if (disallowIntercept || !onInterceptTouchEvent(ev)) {              ev.setAction(MotionEvent.ACTION_DOWN);              final int scrolledXInt = (int) scrolledXFloat;              final int scrolledYInt = (int) scrolledYFloat;              final View[] children = mChildren;              final int count = mChildrenCount;              for (int i = count - 1; i >= 0; i--) {                  final View child = children[i];                  if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                          || child.getAnimation() != null) {                      child.getHitRect(frame);                      if (frame.contains(scrolledXInt, scrolledYInt)) {                          final float xc = scrolledXFloat - child.mLeft;                          final float yc = scrolledYFloat - child.mTop;                          ev.setLocation(xc, yc);                          child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                          if (child.dispatchTouchEvent(ev))  {                              mMotionTarget = child;                              return true;                          }                      }                  }              }          }      }      boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||              (action == MotionEvent.ACTION_CANCEL);      if (isUpOrCancel) {          mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;      }      final View target = mMotionTarget;      if (target == null) {          ev.setLocation(xf, yf);          if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {              ev.setAction(MotionEvent.ACTION_CANCEL);              mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;          }          return super.dispatchTouchEvent(ev);      }      if (!disallowIntercept && onInterceptTouchEvent(ev)) {          final float xc = scrolledXFloat - (float) target.mLeft;          final float yc = scrolledYFloat - (float) target.mTop;          mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;          ev.setAction(MotionEvent.ACTION_CANCEL);          ev.setLocation(xc, yc);          if (!target.dispatchTouchEvent(ev)) {          }          mMotionTarget = null;          return true;      }      if (isUpOrCancel) {          mMotionTarget = null;      }      final float xc = scrolledXFloat - (float) target.mLeft;      final float yc = scrolledYFloat - (float) target.mTop;      ev.setLocation(xc, yc);      if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {          ev.setAction(MotionEvent.ACTION_CANCEL);          target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;          mMotionTarget = null;      }      return target.dispatchTouchEvent(ev);  } 
      看一下 13行 知道事件的分发最先受到onInterceptTouchEvent方法和requestDisallowInterceptTouchEvent的影响的,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?竟然就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。第29-31行说明如果进入了条件判断,可以点击的子view的dispatchTouchEvent方法都是返回的true,那么31行就会执行也直接返回true,那么viewg的dispatchTouchEvent方法就结束了,不再传递事件了。二如果是不能点击的控件,就会走到50行super.dispatchTouchEvent(ev),就是父控件的dispatchTouchEvent方法了。

  在第19行通过一个for循环,遍历了当前ViewGroup下的所有子View,然后在第24行判断当前遍历的View是不是正在点击的View,如果是的话就会进入到该条件判断的内部,然后在第29行调用了该View的dispatchTouchEvent,说明事件的传递从viewGroup-->view(子view)。

  下面再看看view的dispatchTouchEvent方法:



    可以看到7701行是先判断,这边的mOnTouchListener正是我们的给控件设置setOnTouchListener的时候设置进去的,所以可以知道onTouch事件和onTouchEvent事件都是在dispatchTouchEvent方法中执行的,而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行onTouchEvent方法。

  其中控件的onclick事件是在onTouchEvent方法里面执行的,具体的我就不贴源码了,大家也可以看郭霖大神的博客,写的很清楚也很形象。

最后两张流程图来展现我对事件分发的认知:

    子控件的点击:

  

  整个体系的分发过程:

     

    两张截图都是在网上找的,我觉得很形象能更好的帮助自己记忆,最后感谢郭霖大神的博客以及《Android开发艺术探索》,学到了很多也更加知道了自己的不足之处。

   不喜勿喷,仅自勉。

0 0
原创粉丝点击