Android 事件分发机制

1.事件分发机制
事件分发简单而言就是点击事件怎么从Activity流转到ViewGroup,然后流转到View,最后这个点击事件有谁处理,怎么处理?时间分发有三个重要的方法,dispatchTouchEvent(分发事件的方法),onInterceptTouchEvent(拦截事件的方法),onTouchEvent(处理事件的方法)搞清楚Activity,ViewGroup和View的这三个方法,事件分发的机制也就了解了,咱们一个类一个类的进行讲解,首先深呼吸放松一下,然后进入代码的世界,大家慢慢的看。

2.事件分发u型图(网络流行的一个图,能详细反应这个事件分发的流程,我这里直接搬过来了)

在这里插入图片描述

3.Activity层的处理

//很简单捕捉点击向下按下的事件,然后执行个空方法,然后判断Window是不是对事件进行了处理有处理Activity就不管了,没有处理得对事件进行处理

```事件分发由Activity开始
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
   //由window处理分发
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
getWindow只有一个实现类,PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
交给DecorView
   public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    最后流转到ViewGroup的dispatchToucEvent处理

**3.ViewGroup层的处理**

​	
dispatchTouchEvent方法
                //每次Action_Down事件 都会清空标志位如清空disallowIntercept (是否允许拦截)
                //清空mFirstTouchTarget这些操作,所以从点击的瞬间一系列操作跟之前的点击没有关系
    if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState(); 
                
    }

    //处理的结果
    boolean handled = false;
    //伪代码实现 mFirstTouchTarget 点击事件的处理绑定的View 子布局
    //只有是第一次触摸的ACTION_DOWN 按下的瞬间或者 有子的布局处理这个事件的时候 会对事件判断是否拦截
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        //FLAG_DISALLOW_INTERCEPT 通过这个标志判断是否对他进行拦截
        disallowIntercept 默认是false的,可以拦截
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        //只要进入到ViewGroup的 dispatchTouchEvent 这个方法证明上次事件处理是在ViewGroup里面执行的OnTouchEvent返回的true
        //所有默认是拦截的,因为如果dispatchTouchEvent 返回的是false,根本不会到ViewGroup的dispatchTouchEvent方法
        intercepted = true;
    }

//当当前不拦截的时候,并且down事件的时候才会循环遍历每个子View 判断他们对事件是不是进行了处理,在不在这个区域
    if(!intercepted){
        if (action == MotionEvent.ACTION_DOWN) {
            final View[] children = mChildren;
            final ArrayList<View> preorderedList = buildTouchDispatchChildList();//这里面有一个根据Z轴坐标进行排序,z轴越大排在List后面,Z轴越大,代表View越在前面,会遮挡z轴小的,所以从后往前去,后面的view会遮挡前面的View
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(
                        childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(
                        preorderedList, children, childIndex);
                //判断点击的区域是不是在View 的范围内
                if (!child.canReceivePointerEvents()
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }

                //在点击区域内调用 实际调用的是 子View的dispatchTouchEvent
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    //判断是不是子View已经处理过这个事件
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
            }
        }
    }
    //链表保存处理点击事件的VIew,新的处理的VIew放到链表的前面,支持多指触控所以用链表
    //https://www.jianshu.com/p/1b78ccc82b8b (多指触控)
    mFirstTouchTarget指向最新的VIew
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
    //伪代码
    if (mFirstTouchTarget == null) {
        // 没有子View处理,则当前的View处理,真实的代码 最后真实调用的onTouchEvent(ev);
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        //子View处理
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            //在之前是不是已经让子View 处理过了
            handled = true;
        } else {
            //之前是子View处理了,现在判断是不是当前View拦截了,要进行处理
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            //这个时候是move的情况,还没有处理过事件分发给子View处理,然后
            cancelChild为true的话 ,把event.setAction(MotionEvent.ACTION_CANCEL);
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            //如果当前view拦截的话要清空mFirstTouchTarget,以后mFirstTouchTarget就为空了,以后事件就自己处理了,不会经过
            子view处理了, cancelChild 就是当时间分发机制改变的是时候,当前view拦截了点击,清空mFirstTouchTarget 这些东西,然后
            //执行上面那句  if (mFirstTouchTarget == null) {}交给自己处理
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
    }

总结:当点一次点击event是down事件的时候判断ViewGroup是不是对事件进行拦截,如果进行的话进入ViewGroup的OnTouchEvent();如果不进行拦截进入ViewGroup的dispatch方法里面,当event事件为move的时候首先会判断有没有子View 处理这个时间,有的话,还得判断是不是拦截事件,然后依次执行,如果没有子view拦截的时候直接进入自己的OnTouchEvent(前提是会进入ViewGroup的Dispatch方法,进入前提是ViewGroup或者子View会对事件进行处理,如果都没有处理,在ViewGroup的父布局直接就会返回)

②onInterceptTouchEvent拦截方法

是否对事件进行拦截,拦截的话ViewGroup的onTouchEvent对事件进行处理,不拦截的话会调用子view的dispatchTouchEvent

3.View层的处理

①dispatchTouchEvent方法

boolean result;
//首先用ToucheLister判断 对事件处理与否,如果不处理才会调用自身的onTouchEvent
if(mOnTouchListener !=null && ENABLE
        && mOnTouchListener.onTouch(this, event)){
    result = true;
}
if (!result && onTouchEvent(event)) {
    result = true;
}

return result;

②onTouchEvent方法

//是否可以点击CLICKABLE(是否可以点)  LONG_CLICKABLE(是否可以长按)  
    // CONTEXT_CLICKABLE每个子控件的这几个默认的属性不一样
    //具体的得具体分析
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    //控件的状态是Disable的时候不会执行下面的OnClick事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        return clickable;
    }
    //如果是可以点击的状态 直接返回true 对这个事件进行消耗
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                //真实调用的是onClick事件
                performClickInternal();
                break;

        }
        return true;
}

3.总结

上面我对整个事件分发机制整体进行了概括,有的细节比较简略,如果有什么遗漏的重点内容,大家可以在下面进行评论,谢谢大家!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值