android事件分发机制

android的事件分发机制是面试的时候必问的问题,并且在开发的过程正遇到的机遇 跟吃饭一样平常,四年前就知道了这个问题,但是让我说 我还是说不明白,所以还是写一下 没事可以翻出来看看,

那分发的对象是谁呢?也就是TouchEvent 其实就是点击事件,

TouchEvent 有一个down 一个up   不确定有几个的move事件,因为一次触摸按下和抬起手指只有一次,但是可以没有滑动 或者多个滑动

说个比较简单的例子,ViewGroupA嵌套ViewGroupB嵌套View,

那么事件传递顺序就是

ViewGroupA:     dispatchTouchEvent

ViewGroupA:     onInterceptTouchEvent

ViewGroupB:     dispatchTouchEvent

ViewGroupB:     onInterceptTouchEvent

View:                 dispatchTouchEvent

View:                  onTouchEvent

ViewGroupB      onTouchEvent

ViewGroupA      onTouchEvent

大致就是这样,View里面是没有onInterceptTouchEvent的,因为他不会有子类 分发给谁去啊,

直接看代码,先看dispatchTouchEvent

 public boolean dispatchTouchEvent(MotionEvent ev) {

        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 先判断是不是down事件
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            //如果是down事件,那么就证明是一个新的事件 所以把当前的事件置空或者回复初始值

            cancelAndClearTouchTargets(ev);
            //这一步将mFirstTouchTarget 属性置空
            //mFirstTouchTarget 表示是否拦截了事件,拦截==null 不拦截!=null
            resetTouchState();
        }
    //--------------------------------------------------------------------------------------------------
        // 检查是否拦截
        final boolean intercepted;
        //也就是说在第一次点击的时候 或者 我已经拦截了这个事件的时候 才会走这个方法
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //disallowIntercept 表示不允许拦截
            //FLAG_DISALLOW_INTERCEPT 表示禁止Viewgroup拦截除了down以外的事件 子view可以通过requestisallowInterceptD来设置
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                //所以说 只要在down 或者不拦截事件 并且子view没设置不让父容器拦截的时候,才会走到这里
                //调用本身的onInterruptTouchEvent方法
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            //没有触摸目标,这个动作不是初始下降。
            //这个视图组继续拦截触摸。
            intercepted = true;
        }
//--------------------------------------------------------------------------------------------------

        return handled;

    }

接下来看onInterceptTouchEvent

 public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

在满足众多条件后才返回true 一般都返回false ,也就是说 一般都不进行拦截,如果想拦截 你就重新这个方法 让他返回true;

我们继续看viewGroup的dispatchTouchEvent下面的代码

public boolean dispatchTouchEvent(MotionEvent ev) {
//这部分代码 上面已经进行分析了
//--------------------------------------------------------------------------------------------------------------
//获取子view数组
        final View[] children = mChildren;
        //倒叙遍历子view
        for (int i = childrenCount - 1; i >= 0; i--) {
            //我也不知道干啥的 名称的意思是 获取和验证预排序索引 个人感觉就是检测一下索引
            final int childIndex = getAndVerifyPreorderedIndex(
                    childrenCount, i, customOrder);
            //看方法名的意思是 获取和验证预排序视图
            final View child = getAndVerifyPreorderedView(
                    preorderedList, children, childIndex);

            //如果有一个具有可访问性焦点的视图,我们希望它先得到事件,
            // 如果没有处理,我们将执行正常的调度。
            // 我们可以进行双重迭代,但在给定的时间范围内,这是比较安全的
            if (childWithAccessibilityFocus != null) {
                if (childWithAccessibilityFocus != child) {
                    continue;
                }
                childWithAccessibilityFocus = null;
                i = childrenCount - 1;
            }
            //如果子视图可以接收指针事件,则返回true
            //如果子视图在转换到坐标空间时包含指定的点,则返回true。子项不能为空
            //也就是说 看看这个view能不能处理点击事件 不能就算了
            if (!canViewReceivePointerEvents(child)
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                continue;
            }
            //获取指定子视图的触摸目标,如果未找到则返回null
            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
                // Child is already receiving touch within its bounds.
                // Give it the new pointer in addition to the ones it is handling.
                newTouchTarget.pointerIdBits |= idBitsToAssign;
                break;
            }

            //我也不明白
            //重置取消下一个标记
            //如果先前设置了标志,则返回true
            resetCancelNextUpFlag(child);
            //判断是否有子view 有子view的话 就调用子view的dispatchTouchEvnet
            //如果没有子view 则调用supre.disatchTouchEvent
            //得到是否拦截 如果有拦截则走方法里的代码
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                // Child wants to receive touch within its bounds.
                mLastTouchDownTime = ev.getDownTime();
                if (preorderedList != null) {
                    // childIndex points into presorted list, find original index
                    for (int j = 0; j < childrenCount; j++) {
                        if (children[childIndex] == mChildren[j]) {
                            mLastTouchDownIndex = j;
                            break;
                        }
                    }
                } else {
                    mLastTouchDownIndex = childIndex;
                }
                mLastTouchDownX = ev.getX();
                mLastTouchDownY = ev.getY();
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }

            // The accessibility focus didn't handle the event, so clear
            // the flag and do a normal dispatch to all children.
            ev.setTargetAccessibilityFocus(false);
        }
        if (preorderedList != null) preorderedList.clear();
    }

来看一下dispatchTransformedTouchEvent

//如果当前view可以处理事件 则会走到这一步
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
   //省略不必有的代码,其实就是看是否有子view  有点话就调用子view的dispatchTouchEvent
            if (child == null) {
//没有的话 就调用父类的dispatchtouchEvent
 handled = super.dispatchTouchEvent(event);
            } else {
//
     handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        return handled;
    }

如果child为null 就调用super.dispatchTouchEvent(event)其实是调用的View的dispatchTouchEvent(event)

如果child不为null的话 也分两种情况,如果child是ViewGroup的话 那就和咱们一开始分析的一样了

如果child是view的话 那就是走的View的dispatchTouchEvent(event)

所以我们看一下View的dispatchTouchEvent(event)

//View类 
public boolean dispatchTouchEvent(MotionEvent event) {
 // If the event should be handled by accessibility focus first.
     
        boolean result = false;
        //又是那个什么什么过滤触摸事件以应用安全策略  
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
 && li.mOnTouchListener.onTouch(this, event)) {//onTouch默认是返回false的
 result = true;
            }
//注释2
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        return result;
    }

View的dispatchTouchEvent(event)比较简单 因为不需要分发 只需要判断是否处理就好

重点看蓝色部分,

如果设置了OnTouchListener 则先执行onTouchListener的Touch方法,

如果Touch返回true,则将result设置为true,这样的话进不到注释2代码了,所以onTouchEvent也就失效了

如果正常情况下 会走onTouchEvent方法

所以Touch和onTouchEvent这才是真正处理事件的方法

好了 ,目前为止 事件的从上往下传递就结束了,但是这是最底层view是个view,并且消费了此次事件,

如果最底层不是view呢 ? 如果也是个viewGroup那么 事件是如何交给当前viewgroup处理的呢?

我们接着看Viewgroup的dispatch方法

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //上面已经分析过了
        //------------------------------------------------------------------
        //上面已经把viewgroup遍历一遍了,如果有view消费的话 mFirstTouchTarget!=null;
        //或者就是viewgroup拦截了这次事件 就不去遍历子view
         // mFirstTouchTarget==null 证明没有view去消费这个事件
        if (mFirstTouchTarget == null) {
            // 重点看第三个参数 null 也就是child==null
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
        return handled;
    }

看蓝色部分,就是如果当前ViewGroup没有找到消费事件的view,或者是当前ViewGroup拦截了这次事件 就不会去遍历子view

则mFirstTouchTaefet==null,会走dispatchTransformedTouchEvent并且child为null,

我们在上面分析过 如果child为null,则调用自己父类的dispatch方法,也就是view的dispatch方法,从而进入onTouch方法

也就是调用了ViewGroup的onTouch方法,

这样的话 如果都没有处理事件,那么就完成了从下往上传递,

-----------------------------------------------------------------------------------------------------------------

那么还有一个问题,假如当前viewGoup拦截了这次事件,

就会调用自身的OnTouch方法,如果自身的onTouch返回false则还是会调用父控件的onTouche,

那如果自身onTouch返回ture,那么是如和不去调用父view的OnTouch方法的呢,

是因为如果ViewGroupA在遍历子view的时候 发现ViewGroup的dispatchTouch方法返回true,那么就将mFirstTouchTarget!=null,所以就dispatchTransformedTouchEvent的child就不为null,所以就进不去自己的OnTouch方法了

----------------------------------------------------------------------------------------------------------------

分发说完了 我们说一下工作中遇到的问题 怎么解决,

其实也没啥,重写view的onInterceptTouchEvent不就行了吗,想拦截就返回true不想拦截就返回false,

对 就是这样,但是有一点需要注意,判断拦截不拦截应该在TouchEvent的move里判断,因为如果在down中判断,一旦你拦截了那么 move和up就都给当前view处理了,所以在move里拦截 你有更多的选择,


那么还有个问题,就是比如listview里一个viewpage,当viewpage滑动到最后页面后,viewpage就不拦截事件了,讲事件交给父控件去处理,那这会怎么办,也就是说 我要在子控件中控制父控件拦截不拦截,

还记得一开始分析dispatch的时候的disallowIntercept属性吗?就是不允许拦截 只要在它为false的时候 父控件才可以拦截,

disallowIntercept是根据FLAG_DISALLOW_INTERCEPT来控制的,那么我们可不可以让子view通过设置这个属性控制父view拦截不拦截呢? 当然是可以了,那就是通过调用父控件的requestDisallowInterceptTouchEvent方法来控制FLAG_DISALLOW_INTERCEPT,间接的控制disallowIntercept,这样就达到我们想要的效果了

好了 完了











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值