事件分发源码分析(二)

  上一篇我们分析了事件是怎么传到查看的,现在我们来看看在视图里是怎么传递的

   直接在DecorVIew寻找dispatchTouch方法:

@覆盖
    public boolean dispatchTouchEvent(MotionEvent ev){
        最终Window.Callback cb = mWindow.getCallback();
        return cb!= null &&!mWindow.isDestroyed()&& mFeatureId <0cb.dispatchTouchEvent(ev):super.dispatchTouchEvent(ev);
    }
这里的两个方法都调用了一个ViewGroup里面的dispatchTouchEvent:

@覆盖
    public boolean dispatchTouchEvent(MotionEvent ev){
     .....
    //清除此指针标识的早期触摸目标,以防它们出现
    //已经不同步。
                removePointersFromTouchTargets(idBitsToAssign);
       .....
    //调度触摸目标。
            if(mFirstTouchTarget == null){
                //没有触摸目标,因此将其视为普通视图。
                handled = dispatchTransformedTouchEvent(ev,cancel,null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //调度以触摸目标,如果我们已经排除了新的触摸目标
                //发送给它。必要时取消触摸目标。
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while(target!= null){
                    final TouchTarget next = target.next;
                    if(alreadyDispatchedToNewTouchTarget && target == newTouchTarget){
                        句柄= true;
                    } else {
                        final布尔值cancelChild = resetCancelNextUpFlag(target.child)
                                || 截获;
                        如果(dispatchTransformedTouchEvent(ev,cancelChild,
                                target.child,target.pointerIdBits)){
                            句柄= true;
                        }
                        if(cancelChild){
                            if(predecessor == null){
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            继续;
                        }
                    }
                    前任=目标;
                    target = next;
                }
            }
}
在的ViewGroup里面找到了上面的代码,根据注释以及代码可以知道这里是在进行事件分发,如果mFirstTouchTarget为空时,就表示下面没有子视图可传递:

/ **
     *将运动事件转换为特定子视图的坐标空间,
     *过滤出不相关的指针ID,并在必要时覆盖其操作。
     *如果child为null,则假定MotionEvent将被发送到此ViewGroup。
     * /
    私人布尔dispatchTransformedTouchEvent(MotionEvent事件,布尔取消,
            查看孩子,int desiredPointerIdBits){
        最终布尔值处理;
   //取消动作是一种特殊情况。我们不需要执行任何转换
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        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;
        }
     .....
  if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX,offsetY);

                    处理= child.dispatchTouchEvent(事件);

                    event.offsetLocation(-offsetX,-offsetY);
                }
                退货处理;
            }
}
由于这里的孩子为空所以调用了ViewGroup中的父类视图,也就是说顶层查看下面没有子视图消费此事件的话最后事件会由顶层视图消费,但是如果顶层视图不消费呢,我们来看看活动里面代码:

public boolean dispatchTouchEvent(MotionEvent ev){
        if(ev.getAction()== MotionEvent.ACTION_DOWN){
            onUserInteraction();
        }
        if(getWindow()。superDispatchTouchEvent(ev)){
            返回true;
        }
        返回onTouchEvent(ev);
    }
由上面代码可以知道如果顶层视图不消费将交给活动的的onTouchEvent方法处理

我们再来看看mFirstTouchTarget在哪赋值的:

/ **
     *将指定孩子的触摸目标添加到列表的开头。
     *假设目标孩子尚不存在。
     * /
    私人TouchTarget addTouchTarget(@NonNull查看孩子,int pointerIdBits){
        最终TouchTarget target = TouchTarget.obtain(child,pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        回报目标;
    }
当然还有其他地方也赋值了,不过是对它进行重置为空,有兴趣的可以去看下,我们再来看看这个方法在哪被调用了:

 for(int i = childrenCount  -  1; i> = 0; i--){
     ......
  newTouchTarget = addTouchTarget(child,idBitsToAssign);
     ......

}
我们再来看看这个childrenCount:

final int childrenCount = mChildrenCount;
再看看这个mChildrenCount在哪赋值:

private void addInArray(View child,int index){
        查看[] children = mChildren;
        final int count = mChildrenCount;
        final int size = children.length;
        if(index == count){
            if(size == count){
                mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
                System.arraycopy(children, 0, mChildren, 0, size);
                children = mChildren;
            }
            children[mChildrenCount++] = child;
        } else if (index < count) {
            if (size == count) {
                mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
                System.arraycopy(children, 0, mChildren, 0, index);
                System.arraycopy(children, index, mChildren, index + 1, count - index);
                children = mChildren;
            } else {
                System.arraycopy(children, index, children, index + 1, count - index);
            }
            children[index] = child;
            mChildrenCount++;
            if (mLastTouchDownIndex >= index) {
                mLastTouchDownIndex++;
            }
        } else {
            throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
        }
    }
可以看到这里应该是生成子视图数组的方法,看看在哪调用了:

/ **
     *将视图附加到该视图组。附加视图将该组指定为父级,
     *设置布局参数并将视图放入子项列表中
     *它可以通过调用{@link #getChildAt(int)}来检索。
     * <p>
     *这个方法的目的是轻量级的,并且不会假设是否是
     *应重新绘制父母或小孩。正确使用这种方法也将包括制作
     *任何适当的{@link #requestLayout()}或{@link #invalidate()}调用。
     *例如,来电者可以{@link #post(Runnable)发帖}一个{@link Runnable}
     *在所有分离/连接之后在下一帧执行{@link #requestLayout()}
     *调用已完成,导致在重新绘制视图层次结构之前运行布局。
     * <p>
     * This method should be called only for views which were detached from their parent.
     *
     * @param child the child to attach
     * @param index the index at which the child should be attached
     * @param params the layout parameters of the child
     *
     * @see #removeDetachedView(View, boolean)
     * @see #detachAllViewsFromParent()
     * @see #detachViewFromParent(View)
     * @see #detachViewFromParent(int)
     */
    protected void attachViewToParent(View child, int index, LayoutParams params) {
        child.mLayoutParams = params;

        if (index < 0) {
            index = mChildrenCount;
        }

        addInArray(child, index);

        child.mParent = this;
        child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK
                        & ~PFLAG_DRAWING_CACHE_VALID)
                | PFLAG_DRAWN | PFLAG_INVALIDATED;
        this.mPrivateFlags | = PFLAG_INVALIDATED;

        if(child.hasFocus()){
            requestChildFocus(child,child.findFocus());
        }
        dispatchVisibilityAggregated(isAttachedToWindow()&& getWindowVisibility()== VISIBLE
                && isShown());
    }
由注释和方法名证明了我们的判断没错,这里是将子视图添加到父视图的过程,而mChildrenCount是子视图的数量,也就证明了的ViewGroup在事件分发的时候会一层一层往下遍历,但是不可能把事件传给所有子视图啊,继续看:

如果(!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x,y,child,null)){
                                ev.setTargetAccessibilityFocus(假);
                                继续;
                            }
/ **
     *如果子视图可以接收指针事件,则返回true。
     * @隐藏
     * /
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }
 /**
     * Returns true if a child view contains the specified point when transformed
     * into its coordinate space.
     * Child must not be null.
     * @hide
     */
    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
由注释可以看出这两个方法一个是用来判断视图是否可以接受事件,一个是判断事件是否在视图的范围内

这个时候我们再结合上一篇说到调用dispatchTouchEvent方法的地方:

public final boolean dispatchPointerEvent(MotionEvent event){  
        if(event.isTouchEvent()){  
            返回dispatchTouchEvent(event);  
        } else {  
            返回dispatchGenericMotionEvent(event);  
        }  
    }  
往上推导:

private int processPointerEvent(QueuedInputEvent q){  
            最终的MotionEvent事件=(MotionEvent)q.mEvent;  
  
            mAttachInfo.mUnbufferedDispatchRequested = false;  
            mAttachInfo.mHandlingPointerEvent = true;  
            boolean handled = mView.dispatchPointerEvent(event);  
            maybeUpdatePointerIcon(事件);  
            maybeUpdateTooltip(事件);  
            mAttachInfo.mHandlingPointerEvent = false;  
            if(mAttachInfo.mUnbufferedDispatchRequested &&!!mUnbufferedInputDispatch){  
                mUnbufferedInputDispatch = true;  
                if(mConsumeBatchedInputScheduled){  
                    scheduleConsumeBatchedInputImmediately();  
                }  
            }  
            退货处理?FINISH_HANDLED:FORWARD;  
        }  
可以看到true返回FINISH_HANDLED false返回FORWARD,翻译就是结束分发和分发,继续往上看:

  * /
        protected void apply(QueuedInputEvent q,int result){
            if(result == FORWARD){
                向前(Q);
            } else if(result == FINISH_HANDLED){
                完成(q,true);
            } else if(result == FINISH_NOT_HANDLED){
                完成(q,错误);
            } else {
                抛出新的IllegalArgumentException(“无效的结果:”+结果);
            }
        }
由于ViewPostImeInputStage里onProcess方法调用了processPointerEvent并且ViewPostImeInputStage是继承InputStage的,

根据上一篇可以知道事件分发回调用InputStage中应用方法,如上再看看前和结束:

/ **
         *将活动转移到下一个阶段。
         * /
        protected void forward(QueuedInputEvent q){
            onDeliverToNext(Q);
        }
/ **
         *将输入事件标记为已完成,然后将其转发到下一个阶段。
         * /
        保护无效完成(QueuedInputEvent q,布尔处理){
            q.mFlags | = QueuedInputEvent.FLAG_FINISHED;
            如果(处理){
                q.mFlags | = QueuedInputEvent.FLAG_FINISHED_HANDLED;
            }
            向前(Q);
        }
再来看看:

/ **
         *活动正在交付下一阶段时调用。
         * /
        protected void onDeliverToNext(QueuedInputEvent q){
            if(DEBUG_INPUT_STAGES){
                Log.v(mTag,“Done with”+ getClass()。getSimpleName()+“。”+ q);
            }
            如果(mNext!= null){
                mNext.deliver(Q);
            } else {
                finishInputEvent(Q);
            }
        }
这里可以看出如果有事件就继续往下分发,如果没有就结束。这里mNext赋值以及事件消息的mFlags处理就不探究了,不过根据他的命名可以推断一个是结束事件,一个是结束事件分发(如推论有误请麻烦告知)。

到此对于dispatchTouchEvent返回值对事件分发的作用已经说完了,现在再回头看看ViewGroup中的dispatchTouchEvent:

 @覆盖
    public boolean dispatchTouchEvent(MotionEvent ev){
      ......
如果(!disallowIntercept){
                    截获= onInterceptTouchEvent(ev);
                    ev.setAction(动作); / /恢复行动,如果它被改变
                } else {
                    截获= false;
                }
    ......
}
发现onInterceptTouchEvent这个方法,想必大家应该知道,进去看看:

/ **
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     *
     * <p>Using this function takes some care, as it has a fairly complicated
     * interaction with {@link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent)}, and using it requires implementing
     * that method as well as this one in the correct way.  Events will be
     * received in the following order:
     *
     * <ol>
     * <li> You will receive the down event here.
     * <li> The down event will be handled either by a child of this view
     * group, or given to your own onTouchEvent() method to handle; this means
     * you should implement onTouchEvent() to return true, so you will
     * continue to see the rest of the gesture (instead of looking for
     * a parent view to handle it).  Also, by returning true from
     * onTouchEvent(), you will not receive any following
     * events in onInterceptTouchEvent() and all touch processing must
     * happen in onTouchEvent() like normal.
     * <li> For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here
     * and then to the target's onTouchEvent().
     * <li> If you return true from here, you will not receive any
     * following events: the target view will receive the same event but
     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
     * events will be delivered to your onTouchEvent() method and no longer
     * appear here.
     * </ol>
     *
     * @param ev The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     * them dispatched to this ViewGroup through onTouchEvent().
     * The current target will receive an ACTION_CANCEL event, and no further
     * messages will be delivered here.
     */
    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;
    }

根据他的注释可以知道这是一个事件拦截的方法,大概意思就是向下事件会首先传到这里,如果这个方法返回假,之后的事件将继续先传给ViewGroup中,之后在一起传给目标视图的的onTouchEvent处理。如果返回true,则后续的事件将不会传递给此方法,而是和下一起传递到一个ViewGroup中的的onTouchEvent处理,此时目标视图不会接收到任何事件。


的ViewGroup的事件分发已经说完了,下面我们来看看视图里的事件分发。


查看分发

   

public boolean dispatchTouchEvent(MotionEvent event){
     .....
if(li!= null && li.mOnTouchListener!= null
                    &&(mViewFlags&ENABLED_MASK)== ENABLED
                    && li.mOnTouchListener.onTouch(this,event)){
                结果= true;
            }

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

可以看到如果事件传到来看,假如onTouch返回真,则视图会消费事件不会往下传。如果onTouch返回假,而的onTouchEvent返回true,则同样会消费事件。只有这两个同时为假才会不消费事件从而让事件继续往下传。这里先判断onTouch后判断的onTouchEvent,所以的onTouchEvent优先级要高于onTouch。


到此关于事件分发的分析说完了,可能有的地方说的不清楚或者缺漏,甚至有错误,希望大家及时指出,互相交流。



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值