Android开发笔记(三十二)事件分发2:ViewGroup事件分发机制

上一篇我们讲了View的事件分发,我们回忆一下当时总结的结论:

  1.  View事件执行流程

dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick

   2.dispatchTouchEvent的返回值对事件分发的影响

当返回true时,后续的事件,该View还会继续接收到,如Down---Up事件(Down事件已被消费,后续的Up就会继续接收到)

当返回false时,后续的事件,该View再也不能接收到,如Down---Up事件(接收完Down事件后,Up不会再分发给该VIEW,而是把Down事件转发给父容器ViewGroup,如果条件满足,UP事件以后也由ViewGroup接收,从而ViewGroup实现了单击函数)。

回忆着2点总结的原因是:ViewGroup的事件分发与View大部分都是雷同的。唯一的区别是ViewGroup多了一个

public boolean onInterceptTouchEvent(MotionEvent event) 函数,那我们今天研究ViewGroup的事件分发就从onInterceptTouchEvent函数说起。

配套Demo实验源码:https://download.csdn.net/download/gaoxiaoweiandy/11164333

1.    boolean onInterceptTouchEvent(MotionEvent event) 

这个函数通常返回false,表示不拦截事件,言外之意是ViewGroup接收到事件后,会继续传递给它嵌套包含的child视图,通常是一个View。相反,如果返回true,表示拦截,不会再将触摸事件传递给子View,而是由ViewGruop自己处理。这里透漏一下,通常这个函数总是返回false,不拦截。

1.1 返回false(默认总是返回false)

举个例子,有如下一个布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout"
 >

    <Button
        android:onClick="buttonClick"
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dp"
        android:text="Button" />


</RelativeLayout>

      其中的RelativeLayout就是一个ViewGroup,它包含一个chlid子View:Button。   当我们点击Button的时候,DOWN事件先由RelativeLayout接收到,从ViewGroup类的dispatchTouchEvent函数开始,在ViewGroup.dispatchTouchEvent内部调用的onInterceptTouchEvent函数返回false(一般都返回false,后面分析源码再证实),所以RelativeLayout继续将Down事件传递给子View:Button,接下来开始传给Button的dispatchTouchEvent函数(实质是View类的),接下来的执行过程就和我们上一篇分析的View的分发机制一样了,Button最后依次执行了dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。而ViewGroup并没有执行后面的   onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。 所以点击Button时,ViewGroup不会响应单击函数onClick的,因为它将事件传递给了Button,同时Button的dispatchTouchEvent默认返回true,因此后续的UP事件也由Button消费

1.2 返回true

布局不变,但是事件被ViewGroup拦截了,当点击Button的时候,DOWN事件同样先由RelativeLayout接收到,从ViewGroup类的dispatchTouchEvent函数开始,让在ViewGroup.dispatchTouchEvent内部调用的onInterceptTouchEvent函数返回true(可以重写onInterceptTouchEvent强制让它返回true),所以RelativeLayout将事件拦截了下来,不再往下传递给子View:Button了。它自己消费这些事件,最后的结果是ViewGroup执行了dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick这个过程(Tip:这里和View的分发过程又一样了,是因为ViewGroup自己处理事件时调用了super.dispatchTouchEvent,它的super正是View).产生了一个奇怪的效果:我们明明点击的是Button,并没有点击RelativeLayout,但最终RelativeLayout却响应了单击函数。

2.  证明上面的结论

我们还是通过写一个小Demo,通过做实验(改变onInterceptTouchEvent的返回值)且结合ViewGroup源码来得出上面的结论。

2.1  onInterceptTouchEvent返回false

ViewGroup会将事件传递给子View:MyButton。

Demo的布局文件:

<com.example.relativelayoutevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout"
 >

    <com.example.relativelayoutevent.MyButton
        android:onClick="buttonClick"
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dp"
        android:text="Button" />


</com.example.relativelayoutevent.MyLayout>

MainActivity.java

package com.example.relativelayoutevent;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;

public class MainActivity extends Activity implements  OnClickListener, View.OnTouchListener {

	private MyButton myButton;
	private MyLayout myLayout;
	private ImageView imvTest;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		myButton = (MyButton)findViewById(R.id.button1);
		myLayout = (MyLayout)findViewById(R.id.layout);

		myLayout.setOnTouchListener(this);
		myLayout.setOnClickListener(this);

		myButton.setOnClickListener(this);
		myButton.setOnTouchListener(this);

	}



	@Override
	public boolean onTouch(View v, MotionEvent event) {

		boolean returenOnTouch = false;
		Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+" ,result="+returenOnTouch+"----view:"+v);
		return returenOnTouch;
	}

	@Override
	public void onClick(View v) {
		Log.i("eventTest", "OnClickListener----view:"+v);
	}

}

这里分别为RelativeLayout与Button定义了单击响应函数和onTouchListener.onTouch并且返回false,以便能执行到onClick那里,至于为何,在上一篇已经总结过了:dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。

MyLayout.java

package com.example.relativelayoutevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

public class MyLayout extends RelativeLayout {
    public MyLayout(Context context) {
        super(context);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean onTouchEventResult = super.onTouchEvent(event);

        Log.i("eventTest", "MyLayoutonTouchEvent:action--"+event.getAction()+
                ",result="+onTouchEventResult+",view:MyLayout");
        return  onTouchEventResult;//默認返回true
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
       boolean dispatchResult =  super.dispatchTouchEvent(event);   //默认返回true
        Log.i("eventTest", "MyLayoutdispatchTouch:action--"+event.getAction()+
                ",result="+dispatchResult+",view:MyLayout");
       return dispatchResult;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean onInterceptResult = super.onInterceptTouchEvent(event);  //默认返回false

        onInterceptResult = false;
        Log.i("eventTest", "MyInterceptTouch:action--"+event.getAction()+
                ",result="+onInterceptResult+",view:MyLayout");


        return onInterceptResult;

    }
}

这里我们自定义了RelativieyLayout:MyLayout重写了

dispatchTouchEvent  默认返回true,就是说super.dispatchTouchEvent 返回true, Down与后续的UP事件可以顺利接收.
onTouchEvent       默认返回true,就是说super.onTouchEvent  返回true,从而dispatchTouchEvent 返回true.   
onInterceptTouchEvent 默认返回false,就是说super.onInterceptTouchEvent 返回false.默认不拦截事件,将事件交给子View:MyButton。

这里我们显示的将onInterceptResult 指定为false,是为了直观的知道它默认返回false,并且后面做实验时可以随时将返回值改为true.

MyButton.java

package com.example.relativelayoutevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button {

	public MyButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {

		boolean dispatchResult =  super.dispatchTouchEvent(event);
		Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
				",result="+dispatchResult+",view:MyButton");

		return dispatchResult;

	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {

	   boolean onTouchEventResult = super.onTouchEvent(event);  //这个默 认返回true
		Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
				",result="+onTouchEventResult+",view:MyButton");
		return onTouchEventResult;
	}
	
}

这里我们自定义了Button:MyButton重写了

dispatchTouchEvent  默认返回true,就是说super.dispatchTouchEvent 返回true, Down与后续的UP事件可以顺利接收.
onTouchEvent       默认返回true,就是说super.onTouchEvent  返回true,从而dispatchTouchEvent 返回true.   
Tip: View是没有“onInterceptTouchEvent" 这个函数的

Ok,我们现在试试点击Button看一下会打印出什么日志:
eventTest: MyInterceptTouch:action--0,result=false,view:MyLayout
eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
eventTest: MyInterceptTouch:action--1,result=false,view:MyLayout
eventTest: onTouchListener:acton--1 ,result=false,view:MyButton
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
eventTest: OnClickListener----view:com.example.relativelayoutevent.MyButton

同时我们贴出ViewGroup的源码

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    // If the event targets the accessibility focused view and this is it, start
    // normal event dispatch. Maybe a descendant is what will handle the click.
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        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();
        }

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                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;
        }

        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {

            // If the event is targeting accessibility focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        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;
                        }

                        resetCancelNextUpFlag(child);
                        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();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // 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;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

我们日志一行一行来解析:

第1行,DOWN事件(action = 0)进入ViewGroup的dispatchTouchEvent内部,先调用了onInterceptTouchEvent(关键信息已用红色标注),结果onInterceptTouchEvent返回false,不拦截事件。为什么说onInterceptTouchEvent默认会返回false,我们看一下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;
}
首先if语句的第一个条件是事件来源于InputDevice.SOURCE_MOUSE(鼠标),因此onInterceptTouchEvent肯定不会返回true,在手机上用手指点肯定返回的是false。

为了阅读方便,还是把日志复制一份在这里。

eventTest: MyInterceptTouch:action--0,result=false,view:MyLayout
eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
eventTest: MyInterceptTouch:action--1,result=false,view:MyLayout
eventTest: onTouchListener:acton--1 ,result=false,view:MyButton
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
eventTest: OnClickListener----view:com.example.relativelayoutevent.MyButton

第2行日志:将事件传递个子View的dispatchTouchEvent:

         第1行日志已表示ViewGroup不拦截事件,而是继续将DOWN事件(action= 0 )传递给子View(MyButton),于是从ViewGroup.dispatchTouchEvent 内部调用了 child.dispatchTouchEvent,即(MyButton extends View)  view.dispatchTouchEvent,于是接下来的流程就和View事件分发一样了,先执行onTouchListener--onTouch,这里我们在MainActivity里已经强制返回false.以便能执行onTouchEvent.

第3行日志:执行MyButton的onTouchEvent,打印出日志。

第4行,MyButton的dispatchTouchEvent内部执行完onTouchEvent后,递归返回后打印出了日志。

第5行,MyLayout的dispatchTouchEvent,连续调用1,2,3,4步,递归返回后打印出了日志。  

    其实从第2行日志-----第4行日志 都是子view的dispatchTouchEvent在处理事件,我们来看一下MyLayout的dispatchTouchEvent是在哪里将事件传递给了子View的dispatchTouchEvent;请翻阅到ViewGroup源码,我已用红色标注:

for (int i = childrenCount - 1; i >= 0; i--) {

(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)

........

}

  这个for循环遍历ViewGroup内部的各个子View,判断哪一个被点击了,如果被点击了就执行dispatchTransformedTouchEvent,而这个函数里又调用了child.dispatchTouchEvent,即(MyButton)View的事件分发将要开始执行:dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。dispatchTransformedTouchEvent函数的源码如下:

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;
    }

   。。。。。。
}

内部调用了handled = child.dispatchTouchEvent(event); 而handler会默认为true, 因此dispatchTransformedTouchEvent返回true,最终也会使ViewGroup.dispatchTouchEvent会默认返回true.

第6行至第10行日志:UP事件又将上述过程1-5重复了一遍,在此不再赘述。

onInterceptTouchEvent返回false时,DOWN事件与UP事件最终都传递给了MyButton去处理,所以MyButton的onClick得以执行,而RelativeLayout的onClick不会执行。

2.2 onInterceptTouchEvent返回true

其它代码不变,只 修改MyLayout的onInterceptTouchEvent,让其返回true.表示拦截事件,这时我们再点击Button试试。

MyLayout代码及点击Button时的日志如下:

MyLayout.java:

package com.example.relativelayoutevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

public class MyLayout extends RelativeLayout {
    public MyLayout(Context context) {
        super(context);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean onTouchEventResult = super.onTouchEvent(event);

        Log.i("eventTest", "MyLayoutonTouchEvent:action--"+event.getAction()+
                ",result="+onTouchEventResult+",view:MyLayout");
        return  onTouchEventResult;//默認返回true
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
       boolean dispatchResult =  super.dispatchTouchEvent(event);   //默认返回true
        Log.i("eventTest", "MyLayoutdispatchTouch:action--"+event.getAction()+
                ",result="+dispatchResult+",view:MyLayout");
       return dispatchResult;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean onInterceptResult = super.onInterceptTouchEvent(event);  //默认返回false

        onInterceptResult = true;
        Log.i("eventTest", "MyInterceptTouch:action--"+event.getAction()+
                ",result="+onInterceptResult+",view:MyLayout");


        return onInterceptResult;

    }
}

让ViewGroup的onInterceptTouchEvent返回false.

点击Button时的日志如下:

eventTest: MyInterceptTouch:action--0,result=true,view:MyLayout
eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyLayout
eventTest: MyLayoutonTouchEvent:action--0,result=true,view:MyLayout
eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
eventTest: onTouchListener:acton--1 ,result=false----view:com.example.relativelayoutevent.MyLayout
eventTest: MyLayoutonTouchEvent:action--1,result=true,view:MyLayout
eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
eventTest: OnClickListener----view:com.example.relativelayoutevent.MyLayout

从日志上观察,点击MyButton时,竟然MyLayout响应了单击函数,也就是说界面空白处响应了onClick!

好,我们还是来一步一步分析日志:

第1行日志:

DOWN事件(action = 0)进入ViewGroup的dispatchTouchEvent内部,先调用了onInterceptTouchEvent(关键信息已用红色标注),结果onInterceptTouchEvent返回true,拦截截事件,MyButton将不处理DOWN事件,而是MyLayout自己处理。于是打印出了第2行日志。

第2行日志:DOWN事件由MyLayout自己处理,所以先执行了MyLayout的onTouchListener.onTouch,我们在这里强行在MainActivity中返回false.以便MyLayout能执行到onTouchEvent.。

第3行日志:执行MyLayout的onTouchEvent,打印出日志。

第4行,MyLayout的dispatchTouchEvent内部执行完onTouchEvent后,递归返回后打印出了日志。

我们发现从1-4,DOWN事件一直由MyLayout(ViewGroup)来处理,而且处理流程与View的事件分发一样:

dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent

也就是说这时可以把MyLayout(ViewGroup)看作一个View了。为何会这样,我们还是翻上去看一下ViewGroup源,上面已经用红色标注:

    // 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);
            }

这个就是子View没有处理DOWN事件,mFirstTouchTarget 就是null,然后执行

dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);这次第三个参数child被赋值了null,所以dispatchTransformedTouchEvent最终会执行super.dispatchTouchEvent, ViewGroup的super就是View。所以后续把ViewGroup的事件分发按照View的事件分发来执行,然后就执行了MyLayout的onTouchListener.onTouc与MyLayout的onTouchListener.onTouchEvent。

Tip:mFirstTouchTarget 是在这行代码 : newTouchTarget = addTouchTarget(child, idBitsToAssign); 中的addTouchTarget函数内部被赋值的,这行代码的执行恰好是在子View成功处理了Down事件的条件下执行的。

第5,6,7行日志是UP事件的日志,同上面的Down事件一样,都交由ViewGroup来执行,最终还是走的是View的事件分发流程。只是,只是UP事件少调用了一个onInterceptTouchEvent函数,因为在Down事件的时候已经return true表示拦截事件,那么UP事件就不用再调用了,随之也是被ViewGroup拦截。

到此,我们就分析完ViewGroup的事件分发机制了。配套Demo实验源码:https://download.csdn.net/download/gaoxiaoweiandy/11164333

发布了44 篇原创文章 · 获赞 27 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/gaoxiaoweiandy/article/details/89882161
今日推荐