关于事件分发

    我们通过点击一个布局所触发的事件详细流程来分析Android的事件分发机制。

    需要知道的:

    1.Touch事件分发中包含ViewGroup和View:

         ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件.

         ViewGroup继承于View。

         View包含dispatchTouchEvent、onTouchEvent两个相关事件。

        所以只有ViewGroup(RelativeLayout,LinearLayout等属于其子类布局)拥有事件拦截的方法。

        TextView,ImageView等组件继承View

     2一个完整的Touch事件,是由一个Down、一个Up和若干个(可以为0)Move组成的。

        Down方式通过dispatchTouchEvent分发,目的是为了找到真正要处理这个Touch请 求的View。

         当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View.

         之后的Aciton_UP和Action_MOVE将由它处理。

        当所有子 View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup。

    首先假设有这样一个布局:

    

    

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#3ab2ff">
    <Button
        android:layout_width="150dp"
        android:layout_height="80dp"
        android:layout_centerInParent="true"
        android:text="按钮"/>
</RelativeLayout>
 
 在这个布局中根部局是一个RelativeLayout,其中包含一个Button控件。
 
 现在假设在布局上进行了一次点击:
 
 首先执行的是ViewGroup的dispatchTouchEvent()方法:
 
 看下dispatchTouchEvent()相关部分源码:
 
 
 @Override  
   public boolean dispatchTouchEvent(MotionEvent ev) {  
       if (!onFilterTouchEventForSecurity(ev)) {  
           return false;  
       }  
  
       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;  
  
       //进入down事件处理: 
       if (action == MotionEvent.ACTION_DOWN) {  
           if (mMotionTarget != null) {  
               mMotionTarget = null;  
           }  
           // 当以下两种情况发生时进入if方法
           // 1、当前不允许拦截,即disallowIntercept =true,
           // 2、当前允许拦截但是不拦截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;
           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);  
                       //判断你的点击事件(x'yxyzuo'biaoxy坐标)是否在一个组件上,如果是 执行View的dispatchTouchEvent()
                       if (frame.contains(scrolledXInt, scrolledYInt)) {  
                           // offset the event to the view's coordinate system  
                           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;  
                           }  
                      }  
                   }  
               }  
           }  
      } 
可以发现在进入dispatchTouchEvent()进行了如下顺序的判断
是否可以拦截或者onInterceptTouchEvent()【当 onInterceptTouchEvent返回true为拦截本次事件默认为false】中是否进行拦截:
如果进行了拦截则执行ViewGroup的onTouchEvent()方法,就是自己处理了,不再向ViewGroup包含的view中分发。
如果没有进行拦截,则会遍历所有包含的View判断包含的View,判断Down点是否在View上:
(1)如果没有点击在一个组件上,就是点击在了布局的一个空白的地方,最终还是执行 ViewGroup的 onTouchEvent()方法。
(2)如果在一个组建上,则将X,Y传递,将事件分发给View的dispatchTouchEvent();
当View接收分发的事件,执行View的dispatchTouchEvent()方法:
public  boolean dispatchTouchEvent(MotionEvent event) {    
if (mOnTouchListener !=  null && (mViewFlags & ENABLED_MASK) == ENABLED 
&& mOnTouchListener.onTouch( this, event)) {       
return  true;  
}  
return onTouchEvent(event);   
}   
在这个方法中执行了这样的判断:
(1)如果该view设置了 OnTouchListener 事件(clickable==true)并且onTouch()事件返回true的时候,
证明事件已经被消费了,不再执行View的 onTouchEvent();
(2)否则则执行View的onTouchEvent()方法:
public boolean onTouchEvent(MotionEvent event) {         ...  
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
      switch (event.getAction()) {               case MotionEvent.ACTION_UP:                               break;               case MotionEvent.ACTION_DOWN:               break;                  case MotionEvent.ACTION_MOVE:                   break;           }       return ture;    
}
return false; }  
可以看到在这里,我们就可以对我们触摸事件ACTION_UP,ACTION_DOWN,ACTION_MOVE等操作进行处理。
OnTouchListener 事件中 ,当我们:
(viewFlags & CLICKABLE) == CLICKABLE 支持点击
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) 支持长按
返回true.
总结一个点击事件的流程就是:
1.ViewGroup的dispatchTouchEvent()如果点击的空白区域不是包含的View上或者dispatchTouchEvent()
方法返回true进行拦截,则本身消费执行onTouchEvent()
否则执行 2 .
2.View接收到事件执行dispatchTouchEvent(),如果被View的OnTouchListener()消费掉,
则事件执行完毕,不再执行View的onTouchEvent();
否则执行 3 .
3.View的onTouchEvent接受到事件进行处理,如果返回true,事件被消费,
不再执行其他事件,表示ViewGroup是无法处理这个事件已经被消费掉了。
如有问题,敬请指正!
                          










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值