Android事件分发源码分析(补充篇)

  很久没有写博客了,最近复习之前的这篇时间分发机制的时候发现一些细节问题,然后对它进行补充,然后综合起来应该是最为详细的时间分发机制解析了。
之前写过一篇关于Android事件分发源码分析比较清晰的分析了android的事件分发机制。
  但是实际测试中,发现一个现象问题,我们创建一个TouchViewGroup继承自LinearLayout,然后重写里面的onInterceptTouchEvent和onTouchEvent方法,代码如下所示

public class TouchViewGroup extends LinearLayout{
    public TouchViewGroup(Context context) {
        this(context,null);
    }

    public TouchViewGroup(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public TouchViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept=false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("@wubo onInterceptTouchEvent:","ACTION_DOWN");
                intercept=false;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("@wubo onInterceptTouchEvent:","ACTION_MOVE");
                intercept=true;
            case MotionEvent.ACTION_UP:
                Log.e("@wubo onInterceptTouchEvent:","ACTION_UP");
                intercept=false;
                break;
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handle=false;
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("@wubo TouchViewGroup:","ACTION_DOWN:"+event.getEdgeFlags());
                handle=false;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("@wubo TouchViewGroup:","ACTION_MOVE:"+event.getEdgeFlags());
                handle=false;
                break;
            case MotionEvent.ACTION_UP:
                Log.e("@wubo TouchViewGroup:","ACTION_UP");
                handle=false;
                break;
        }
        return handle;
    }
}

然后再创建一个TouchView继承自定义View重写onTouchEvent方法,代码如下

public class TouchView extends View {
    private int icount;
    public TouchView(Context context) {
        this(context,null);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handle=false;
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                event.setEdgeFlags(444);
                Log.e("@wubo TouchView:","ACTION_DOWN");
                handle=true;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("@wubo TouchView:","ACTION_MOVE");
                handle=true;
                break;
            case MotionEvent.ACTION_CANCEL:
                event.setEdgeFlags(111);
                Log.e("@wubo TouchView:","ACTION_CANCEL");
                handle=false;
                break;
            case MotionEvent.ACTION_UP:
                Log.e("@wubo TouchView:","ACTION_UP");
                handle=true;
                break;
        }
        return handle;
    }
}

可以看到在TouchViewGroup中我们重写了拦截方法,没有拦截down事件但是把move事件给拦截了,而在TouchView中move事件实际是被消费的,使用TouchViewGroup包裹TouchView之后实际运行打印运行日志会发现TouchView的move事件并未被打印而TouchViewGroup中的move事件却打印了,奇怪吧,按照常理思想TouchViewGroup虽然拦截了事件,但是并没有消费掉down事件,并且拦截的也不是down事件呀,我一开始按照之前源码中的事件流程思考了一下这种场景:
TouchView-> down事件(消费) ->TouchViewGroup (mFirstTouchTarget=TouchView)
move事件来临的时候此时由于mFirstTouchTarget !=null 就不会抛给TouchViewGroup处理,自己会调用

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

        // Canceling motions is a special case.  We don't need to perform any transformations
        // 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;
        }

        ...
        return handled;
    }

方法去处理,而第二个参数在由于在TouchViewGroup中move事件被拦截所以intercepted这个值为true,第二个参数cancel传递进去的也是true,这个方法里面执行逻辑会把事件里面本身move action取出,然后把这个事件赋值为cancel事件传递给TouchView去处理然后在把事件还原设置回来
在ViewGroup的dispatchTouchEvent方法中有这么段代码

 // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                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;
                }
            }

如果拦截这个事件的话就会把mFirstTouchTarget赋值为mFirstTouchTarget.next也就是置空,因为mFirstTouchTarget.next=null.
由于之前TouchView消费了down事件所以TouchViewGroup和TouchViewGroup所有的父layout里面的mFirstTouchTarget都会层层赋值,此时TouchViewGroup的mFirstTouchTarget=null,但是包裹TouchViewGroup的layout中的mFirstTouchTarget=TouchViewGroup,此时后续move事件继续来临的时候就会认为是TouchViewGroup消费了down事件,down后面的一系列事件序列都会交给TouchViewGroup去处理,这也是为什么前面明明在TouchViewGroup中的onTouchEvent的事件中,down和move没有返回true去消费事件,但是后面的move事件都打印的原因。

这里提几个小问题,通过前面分析知道ViewGroup是根据当前事件是不是down事件或者mFirstTouchTarget的值是否为null进行判断的,当拦截一次move事件后此时后面事件既不是down事件,并且mFirstTouchTarget值为null,那么后续事件序列还能不能分发到TouchView中去处理呢?

你可能感兴趣的:(Android)