2019-01-16ViewGroup事件分发

2019-01-16  本文已影响0人  猫KK

ViewGroup 事件分发

ViewGroup 事件分发主要又三个方法

    @Override
    //用于分发事件
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

    @Override
    //判断是否拦截
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    //用于消耗事件
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

相比view 增加了onInterceptTouchEvent 方法,这个方法是根据该方法的返回值来判断是否需要拦截事件。onTouchEvent 方法ViewGroup 没有重写,直接使用的View.onTouchEvent,关于onTouchEvent分析请看上一篇

dispatchTouchEvent

来分析dispatchTouchEvent ,这个方法被ViewGroup 重写了

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
           // 省略代码.......
        boolean handled = false;
        //整个方法都在这个if 里面,所以肯定会进来
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            //首先判断是否为 DOWN 事件 如果是则做一些初始化操作
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
         //省略代码......
    }

先来分析这么多,事件的开始都是从按下开始,所以第一个事件就是ACTION_DOWN,当为 DOWN 事件是,来看看做了什么

     /**
     * Cancels and clears all touch targets.
     * 取消所有的targets
     */
    private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
           //省略代码 ......
           //清除Targets
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }

    /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
           //最终将mFirstTouchTarget 置为 null
            mFirstTouchTarget = null;
        }
    }

所以cancelAndClearTouchTargets 方法就是将mFirstTouchTarget 置为null
继续往下看

           //用于判断是否拦截
            final boolean intercepted;
            //第一个事件就是 DOWN 事件,mFirstTouchTarget 还没有赋值,所以为 null
            //所以当事件为 DOWN 时,这个判断肯定会为true
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {  
                //这一个标志位,是子类请求父类不要拦截事件使用
                //在前面 DOWN 事件初始化时候调用方法 resetTouchState() 将 mGroupFlags 置为           
                //FLAG_DISALLOW_INTERCEPT 所以在子布局没有修改的前提下 disallowIntercept 为false
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //调用自身的onInterceptTouchEvent 方法
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); 
                } else {
                    //如果子布局请求父布局不拦截,将 intercepted 置为false
                    intercepted = false;
                }
            } else {
                //如果当前事件不为 DOWN 并且 mFirstTouchTarget == null,直接将 intercepted 置为 true
                intercepted = true;
            }

对于 disallowIntercept 这个值,当我们想父类不拦截事件是可以这样子写

    //请求父类不要拦截事件
   getParent().requestDisallowInterceptTouchEvent(true);
  
   //ViewGroup 中,这个方法只是用来改变mGroupFlags 的值
   @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        //如果 mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为true,说明 mGroupFlags 已经修改了
        //直接返回
        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }
        //修改 mGroupFlags 的值
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

当我们请求父类不拦截事件时 intercepted 的值就恒为true
继续往下看

            // Check for cancelation.
            //判断是否取消,不需要,为true
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
             // mGroupFlags 的值为 FLAG_DISALLOW_INTERCEPT 所以 split 为 true
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
           // 来这里 canceled 默认为true intercepted 的值根据上面执行的方法得到,现在假设为false
            if (!canceled && !intercepted) {
                    final int childrenCount = mChildrenCount;
                   //newTouchTarget 为null 子view 的数量不为0
                    if (newTouchTarget == null && childrenCount != 0) {
                        //倒序循环子view 
                        for (int i = childrenCount - 1; i >= 0; i--) {
                           // getTouchTarget 里面代码简单,可以知道newTouchTarget还是为null
                            newTouchTarget = getTouchTarget(child);
                           //所以进不来
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            resetCancelNextUpFlag(child);
                            //注意看这里,后面分析
                            //第二个参数为false child不为null
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               // addTouchTarget 里面相当于new 了一个对象分别给mFirstTouchTarget 和newTouchTarget 赋值
                                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);
                        }
                }
            }

来看dispatchTransformedTouchEvent 这个方法干了什么

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        //将当前的事件赋值给 oldAction
        final int oldAction = event.getAction();
        //注意这里,是用来处理 ACTION_CANCEL 事件的
        //根据传过来的参数,cancel 为false 事件为DOWN 所以不会进来这个判断
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        //省略代码.....

        //根据传过来的 child 不为 null
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            //调用 child 的 dispatchTouchEvent 方法
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // 释放
        transformedEvent.recycle();
        //返回子布局的 dispatchTouchEvent 的返回值
        return handled; 
    }

所以到这里,当按下屏幕产生一个 down 事件,这个事件会从最外层的ViewGroup 中传递下来,调用子view 的dispatchTouchEvent 方法,如果当前子view 也为一个ViewGroup ,并且dispatchTouchEvent 方法没有返回true,那么该子view 又会调用自身子view 的dispatchTouchEvent方法,直到最后一个子 view 调完dispatchTouchEvent 方法。这是一个责任链的设计模式。回到前面

    //当 dispatchTransformedTouchEvent 返回true 时才会进来,
    // dispatchTransformedTouchEvent 的返回值又由 onTouchEvent 等方法决定
    //可以看上一片view的事件分发
    //这里面主要的操作就是给mFirstTouchTarget 赋值
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                //省略代码.....

                              
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // addTouchTarget 方法主要就是创建一个 TouchTarget 并赋值给 mFirstTouchTarget
                                //并将该 TouchTarget 返回,所以当前DOWN事件来说 newTouchTarget == mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

现在假定dispatchTransformedTouchEvent 返回true ,此时mFirstTouchTarget 不为null,继续往下

           // 此时mFirstTouchTarget 不为null
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                   //根据上面可以直到newTouchTarget == target
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                       
                    }
                    predecessor = target;
                    //next 为null 跳出循环
                    target = next;
                }
            }

小总结 : 到这里一个 DOWN 事件完结,当 DOWN 事件发生时:这个事件会到达最外层的ViewGroup中,根据mGroupFlags的值来判断是否有子类需要消耗事件,如果没有,则调用自身的onInterceptTouchEvent方法,根据该方法的返回值判断是否需要拦截该事件。

  1. 如果返回 true :说明拦截改事件,就不会调用子类布局的 dispatchTouchEvent 方法,mFirstTouchTarget 的值 null ,后面因为mFirstTouchTarget 为null 调用 dispatchTransformedTouchEvent 方法是传的child为null,就会调用 super.dispatchTouchEvent 也就是view的 dispatchTouchEvent,之后就是view的事件分发流程
  2. 如果返回false:说明不需要拦截事件,则会调用子类布局的 dispatchTouchEvent ,即将事件分发到子布局中,之后对于自身来说,根据子布局的 dispatchTouchEvent 方法的返回值,判断是否需要给 mFirstTouchTarget 赋值,再之后根据 mFirstTouchTarget 的值来判断是否调用自身的 super.dispatchTouchEvent 方法

当 down 事件完成,来看第二个事件move事件

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    //当前不为ACTION_DOWN ,所以不会将mFirstTouchTarget 置为null 
   //根据上面假设的 子布局 dispatchTouchEvent 方法返回true,所以 mFirstTouchTarget 的值不为null
    if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

    final boolean intercepted;
      //mFirstTouchTarget != null 为true 所以会进入这个判断
      if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
        //判断标志位 mGroupFlags
         final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                    //调用自身的 onInterceptTouchEvent 方法
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
             } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
    //根据 intercepted 判断是否调用子view的 dispatchTouchEvent 事件
    if (!canceled && !intercepted) {
        //当前为 MOVE 事件,第一个判断为false
        // split 为false 第二个判断为false
        //当前为 MOVE 事件 第三个判断为false
        //所以这一段不会进来
        if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            //省略代码....
       }
    }
    //当前 mFirstTouchTarget 不为null
    if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //newTouchTarget 没有被赋值 所以为null ,所以这个判断为false
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        //intercepted 为false ,resetCancelNextUpFlag 也为false
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //调用 dispatchTransformedTouchEvent 方法 child 不为null
                        //其实就是调用子view 的 dispatchTouchEvent 方法,假设返回true
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        // cancelChild 为false
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
    //返回 handled 为true
    return handled;
}

到此 MOVE 事件也从父布局分发到子布局中。那么父布局拦截了其中一个事件,之后的事件子布局都收不到,这是怎么实现的呢?比如,我在父布局中这样子:

     @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            Log.e("TAG", "onInterceptTouchEvent: ------>拦截了move事件");
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

这样会如何呢?注意需要在 onTouchEvent 方法中返回true,否则这个判断是不会进来的。首先分析 DOWN事件,DOWN 事件和前面事件一样分发,到MOVE事件时,

    //因为当前 onInterceptTouchEvent 返回true 所以 intercepted 为true
    if (!canceled && !intercepted) {
        //省略代码.....
    }
    //省略代码....

    //DOWN事件之后 mFirstTouchTarget 不为null
    if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //newTouchTarget 为null
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        //intercepted 为true 所以 cancelChild 为true
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //调用 dispatchTransformedTouchEvent 方法,cancelChild 为true,child不为null
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        //cancelChild 为true
                        if (cancelChild) {
                            //predecessor 为null
                            if (predecessor == null) {
                                //next 为null,所以 MOVE 事件之后 mFirstTouchTarget 为null
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            //将target 赋值为null 跳出循环
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

来看 dispatchTransformedTouchEvent 方法,当cancelChild 为true ,child 不为null时

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        //oldAction 为MOVE
        final int oldAction = event.getAction();
        //cancel 为true,所以会进来
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            //将当前事件设置为ACTION_CANCEL
            event.setAction(MotionEvent.ACTION_CANCEL);
            //child 不为null
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                 //调用 dispatchTouchEvent 方法,注意当前的 event 为 ACTION_CANCEL
                handled = child.dispatchTouchEvent(event);
            }
            //将当前的事件设置为 MOVE
            event.setAction(oldAction);
            return handled;
        }

        //省略代码....
    }

所以子布局会收到一次 ACTION_CANCEL 事件。再时候会将 mFirstTouchTarget 置为null,子布局就收不到事件了。

上一篇下一篇

猜你喜欢

热点阅读