View的事件分发机制

一、点击事件的传递规则

当点击事件(MotionEvent)发生时,最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件的派发。过程如下:

当前Activity->Activity内部的Window(实现类PhoneWindow)->DecorView->顶级View

Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,所以可以直接当成ViewGroup处理。

View的事件分发只有两个主角:ViewGroup和View,分发过程由三个很重要的方法来一起完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。


1、dispatchTouchEvent

如果事件能传递给当前View/ViewGroup,那么此方法一定会被调用。对于ViewGroup,若不拦截此事件,会遍历它的子View,调用子view的dispatchTouchEvent方法。

上图的消息下发顺序为:①-②-⑤-⑥-⑦-③-④

而dispatchTouchEvent方法为boolean类型,表示是否消耗当前事件,若返回true则顺序下发中断。

比如上图若⑤的dispatchTouchEvent返回true,那么⑥-⑦-③-④将接收不到当前事件。


ViewGroup的dispatchTouchEvent真正在执行“分发”工作,而View并不执行或者说它分发的对象为自己,决定是否把touch事件给自己处理,处理的方法为onTouchEvent事件;

而这里需要留意的是,View会先判断有没有设置OnTouchListener,若OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会调用,可见OnTouchListener的优先级高于onTouchEvent。

相关源码如下:

ListenerInfo li = mListenerInfo;
    if
(li != null && li.mOnTouchListener!=null
           
&& (mViewFlags & ENABLED_MASK) == ENABLED
           
&& li.mOnTouchListener.onTouch(this,event)) {
        result = true;
   
}

    if (!result && onTouchEvent(event)) {
        result = true;
   
}
}


一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent中处理它。


当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。


在目前的情况看来,似乎只要我们把所有的onTouchEvent都返回false,就能保证所有的子控件都响应本次Touch事件了。但必须要说明的是,这里的Touch事件,只限于Acition_Down事件,即触摸按下事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。


2、onInterceptTouchEvent

只有ViewGroup才有此方法。

onInterceptTouchEvent有两个作用:

(1).拦截Down事件的分发。

假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的所有下发操作拦截掉,这种情况下,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的onTouchEvent事件被执行。这种情况下可以把这个ViewGroup直接当成View来对待。


(2).中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

假如我们在某个ViewGroup的onInterceptTouchEvent中,将Acion为Down的Touch事件都返回false,其他的都返回True,这种情况下,Down事件能正常分发,若子View都返回false,那mTarget还是为空,无影响。若某个子View返回了true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move(如果上一个操作不是UP)将由ViewGroup的onTouchEvent处理。


二、事件分发的源码分析(简化)

(1)ViewGroup.dispatchTouchEvent(event)

boolean dispatchTouchEvent(MotionEvent event) {

    int action = event.getAction();

    

    //判断ViewGroup是否拦截touch事件。当为ACTION_DOWN或者找到能够接收touch事件的子View

    时,由onInterceptTouchEvent(event)决定是否拦截。其他情况,即ACTION_MOVE/ACTION_UP

    没找到能够接收touch事件的子View时,直接拦截。

    boolean intercepted;

    if (action == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {

        intercepted = onInterceptTouchEvent(event);

    } else {

        intercepted = true;

    }


    //如果ViewGroup不拦截touch事件。在ACTION_DOWN时遍历所有子View,查找能够接收touch事件的

    View。如果找到则设置mFirstTouchTarget,并跳出循环。

    boolean alreadyDispatchedToNewTouchTarget =  false;

    if (!intercepted) {

        if (action == MotionEvent.ACTION_DOWN) {

           for (int i = childrenCount - 1; i >= 0; i--) {

                if (!canViewReceivePointerEvents(child) ||

                 !isTransformedTouchPointInView(x, y, child, null)) {

                     continue;

                }

                if (dispatchTransformedTouchEvent(event, child)) {

                   //找到mFirstTouchTarget

                   newTouchTarget = addTouchTarget(child);

                   alreadyDispatchedToNewTouchTarget = true;

                   break;

                }

             }

         }

    }


    //事件下发及消费。如果没找到能够接收touch事件的子View,则由ViewGroup自己处理及消费。

    如果找到能够接收touch事件的子View,则由子View递归处理touch事件及消费。

    boolean handled = false;

    if (mFirstTouchTarget == null) {

        handled = dispatchTransformedTouchEvent(event, null);

    } else {

        if (alreadyDispatchedToNewTouchTarget) {

            handled = true;

        } else {

            while (touchTarget) {

                handled = dispatchTransformedTouchEvent(event, child);

            }

        }

    }


    return handled;

}



//ViewGroup事件下发。如果无接收touch事件的子View,则由ViewGroup的父类(即View)下发touch事件

如果child非空,则交由子View下发touch事件,子View可以是ViewGroupView

boolean dispatchTransformedTouchEvent(MotionEvent event, View child) {

   boolean handled;

   if (child == null) {

        handled = super.dispatchTouchEvent(event);

   } else {

        handled = child.dispatchTouchEvent(event);

   }

   return handled;

}

(2)View.dispatchTouchEvent(event)

//ViewTouch事件分发。当外部设置了mOnTouchListener时,先交由mOnTouchListener.onTouch(event)消费。

若未消费,则交给ViewonTouchEvent(event)消费。onTouchEvent的实现是,如果设置了mOnClickListener

则执行mOnClickListener.onClick()点击事件。返回值为true,表示消费,否则未消费。

boolean dispatchTouchEvent(MotionEvent event) {

   boolean result = false;

   if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {

         result = true;

   }

   if (!result && onTouchEvent(event)) {

        result = true;

   }

   return result;

}



boolean onTouchEvent(MotionEvent event) {

   performClick();

}


三、总结

1、Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。

2、ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

3、触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

4、当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

5、当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。

6、当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值