Android MotionEvent传递流程 源码笔记

45 篇文章 0 订阅
22 篇文章 0 订阅
  1. ViewGroup的boolean dispatchTouchEvent(MotionEvent ev):

    • mInputEventConsistencyVerifier是处于debug目的使用,可以忽略。
    • onFilterTouchEventForSecurity(MotionEvent event)来检测这个MotionEvent是否应该被dispatch. 只有通过了,才能继续向下走.
    • 使用action & MotionEvent.ACTION_MASK来获取该MotionEvent的Action,使用& MotionEvent.ACTION_MASK的原因是屏蔽poniter index 这些信息,因为这里不需要,只需要知道Action的类型,等同于MotionEvent的getActionMasked().
    • 如果发现是ACTION_DOWN,那么说明是新一轮的Touch事务的开始, 因为一次完整的Touch事务必然以down开始,而且也只能有一次down,调用cancelAndClearTouchTargets(ev)来将之前所有的相关state全部clear, 将有可能会将上一次Touch事务的up/cancel event drop掉, 而resetTouchState()则被调用来重置所有的Touch相关变量和状态(包括清除了所有的TouchTarget)
    • 如果是ACTION_DOWN或者mFirstTouchTarget不是null,对于ActionDown, 必然是一轮MotionEvent的第一个,因此需要尝试进行intercept, 而如果mFirstTouchTarget不是null代表着已经有ChildView声明自己会接受这一轮的事务了,因此也需要进行intercept的尝试(从这里可以推断出,如果在DOWN的时候,childView没有响应者(导致mFirstTouchTarget == null),那么本轮之后的MotionEvent都会直接走到另外一个分支: intercepted直接设置为true, 导致不会再尝试遍历ChildView进行dispatch,ChildView永远失去了对本轮MotionEvent的知晓), 那么会尝试进行InterceptTouchEvent, 不过之前要做一次disallowIntercept的判断(mGroupFlags & FLAG_DISALLOW_INTERCEPT, viewgroup的requestDisallowInterceptTouchEvent()方法改变的就是这个flag),, 如果允许,就会调用onInterceptTouchEvent(ev)这个经常被使用的函数, 并且会将结果赋给intercepted, 传入的MotionEvent的Action会被强制restore,以防止onInterceptTouchEvent中修改了action的类型
    • 如果!disallowIntercept,那么intercepted = false
    • 如果当前没有任何的touch target(mFirstTouchTarget == null),并且action 也不是一个DOWN, 那么说明之前DOWN的motionEvent没有被其他的ChildView所处理,那么这一轮的Event都会直接交给ViewGroup本身来处理,即也是”intercept”了,代表的意思就是不用再对childView进行相关的判断了,因此parent已经将这轮MotionEvent吃掉了.
    • canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL, 即如果PFLAG_CANCEL_NEXT_UP_EVENT这个标志位被设置了,那么这一轮MotionEvent也会被强制cancel.
    • split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
    • 如果没有被cancel,也没有被intercept,这意味着该MotionEvent是有可能传递给ChildVide的,因此下面就是对ChildView的遍历处理:
      • 如果是ACTION_DOWN, 或者split && actionMasked = MotionEvent.ACTION_POINTER_DOWN 或者actionMasked = MotionEvent.ACTION_HOVER_MOVE,对按下的特殊逻辑处理, 这是这一轮里的第一个事件,谁最终接收了它就基本上就确定了这一轮最终的处理位置.
      • 获取ActionIndex, removePointersFromTouchTargets(idBitsToAssign)
      • 下面的操作就是将挨个遍历当前的Child(getChildDrawingOrder()可以改变遍历ChildView的顺序)(* 对于不可见或者及当前有动画的(canViewReceivePointerEvents(..))或者当前的落点不在范围内的(isTransformedTouchPointInView(…), 会考虑parentView的scrollX/Y的影响)ChildView直接ignore*), 将可以dispatch的Touch Event进行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign), 如果返回true,代表这个ChildView声明自己会处理这一次的TouchEvent,那么会直接break出去childView的遍历处理,并且newTouchTarget = addTouchTarget(child, idBitsToAssign), 将这个child保存为TouchTarget, 在这一轮后续的MotionEvent到来时,这个TouchTarget会被check以能将后续的MotionEvent直接投递给已经宣称自己接受这一轮MotionEvent的ChildView
      • 如果发现newTouchTarget == null(及这次没有一个child可以被dispatch此TouchEvent, 那么和会找到之前最后一次添加的newTouchTarget)
    • 如果mFirstTouchTarget == null,即没有任何的TouchTarget可以来进行dispatch此MotionEVent,那么会调用自己的dispatchTransformedTouchEvent(ev, canceled, null(如果是ChildView话,这里就填入ChildView引用),
      TouchTarget.ALL_POINTER_IDS)
    • 否则挨个遍历当前的TouchTarget(其实是一个链表, 以mFirstTouchTarget为头), 这表明,一个MotionEvent是可以传递给多个ChildView的, 对于在上面已经dispatch过Event的ChildView(一般会被加为newTouchTarget)会直接pass处理下一个, 向TouchTarget指定的View调用dispatchTransformedTouchEvent(…),还会顺带检测cancelChild = resetCancelNextUpFlag(target.child) || intercepted, 这个flag的使用说明了如果parentView在一轮Event过程中途intercept了ChildView的MotionEvent(即这一轮之前的MotionEvent给了ChildView处理), 那么ChildView就会收到一个ACTION_CANCEL, 如果要cancel, 会将该TouchTarget从链表中remove并回收TouchTarget.
  2. ViewGroup的dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits):

    • 如果child是null,那么调用super.dispatchTouchEvent(event)(即View定义的dispatchTouchEvent(event))
      *否则调用 child.dispatchTouchEvent(event)
    • 如果是childView,那么还会将ViewGroup的ScrollX/Y考虑进去,对要dispatch的MotionEvent的X/Y进行累加来的到肉眼观察上合理的X/Y
  3. View的boolean dispatchTouchEvent(MotionEvent event):

    • 注释里解释了返回值的意义: True if the event was handled by the view, false otherwise.
    • 对于输入的event, 会先进性onFilterTouchEventForSecurity(event)过滤:
      • 如果mViewFlags中有FILTER_TOUCHES_WHEN_OBSCURED并且输入的MotionEvent的Flag也有FLAG_WINDOW_IS_OBSCURED这个标记,那么返回false, 代表这个MotionEvent不进行处理,dispatchTouchEvent(…)会直接返回false直接放弃对这一轮MotionEvent的处理.
    • 如果通过了FilterSecurity, 那么会优先处理mOnTouchListener(在ViewENABLED的前提下),如果mOnTouchListener的onTouch(…)函数返回true, 那么也直接返回true表明接手这一轮MotionEvent, 否则还会交给onTouchEvent(event)
      • 如果onTouchEvent(event)还能返回true的话,还会接手这一轮MotionEvent.
      • 否则如果mOnTouchListener和onTouchEvent(event)都返回了false, 那么代表View放弃对这一轮MotionEvent的处理,返回false.
  4. ViewRootImpl在收到Event的时候,调用的是DecorView的dispatchPointerEvent(MotionEvent event)(通过processPointerEvent(QueuedInputEvent q)):

    • 如果MotionEvent是event.isTouchEvent(), 那么调用DecorView的 dispatchTouchEvent(event)(再次验证了dispatchTouchEvent的入口地位), 入口应该是DecorView的dispatchTouchEvent, 但是后面会看到,它在大多数情况也仅仅是无脑转发.
    • 否则调用View的dispatchGenericMotionEvent(event).
  5. DecorView本身extends FrameLayout, 并且也对dispatchTouchEvent(…)进行了简单的修改:

    • getCallback()
    • 如果是有callback,并且没有isDestroyed()并且mFeatureId < 0(Feature = -1代表它是Application的 DecorView)才会转而调用cb.dispatchTouchEvent(ev)
    • 否则直接调用super.dispatchTouchEvent(ev)(FrameLayout没有修改该函数,因此这个函数将是ViewGroup的dispatchTouchEvent(ev))
    • 上面的getCallback()和isDestroyed()均是Window类中定义的函数.
    • 那么正常启动一个Activity时,会在被attach的时候通过PolicyManager.makeNewWindow(this)来生成一个PhoneWindow, 并且将其callback改为Activity自己, Activity实现了Window.Callback接口
    • 那么接着上面的逻辑,在普通的Activity中,DecorView的dispatchTouchEvent(ev)会转发给Window Callback(这里是Activity)的dispatchTouchEvent(..)
    • Activity的dispatchTouchEvent(..):
      • 如果MotionEvent的是ACTION_DOWN, 那么会调用onUserInteraction()代表用户现在开始交互了
      • 如果getWindow().superDispatchTouchEvent(ev)返回true, 那么callback返回true代表这一次的MotionEvent被处理了,注意这时候尚未进入dispatch体系流程,因此不是一轮
      • 如果返回false, 那么返回Activity的onTouchEvent(ev).
      • Activity本身的onTouchEvent(..)处理很简单(注释也说明了其含义Called when a touch screen event was not handled by any of the views under it. This is most useful to process touch events that happen outside of your window bounds, where there is no view to receive it., DIalog的点击区域外自动关闭其实跟这个差不多):
        • 如果Activity的mWindow的shouldCloseOnTouch(this, event))返回true,那么调用finish(),并且返回true,因为这个event是被处理了的.
          • Window的shouldCloseOnTouch(this, event))函数:
            • 如果设定了mCloseOnTouchOutside
            • 如果event是ACTION_DOWN
            • 如果isOutOfBounds(context, event)
            • 如果peekDecorView(..)返回为非空表明现在是有一个DecorView的.
            • 那么就会返回true表明可以关闭这个Activity.
        • 否则返回false.
      • 再看看还可能被调到的Window的superDispatchTouchEvent(..):
        • 抽象函数,具体实现看PhoneWindow: 调用了DecorView的superDispatchTouchEvent(…), DecoreView进而调用了super.dispatchTouchEvent(…), 饶了一圈,终于进入了dispacth逻辑体系了,Activity中间横插了一脚,其实Activity等于加了一个预处理(onUserInteraction)和一个后处理(Activity的onTouchEvent)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值