Android开发笔记(三十一)Android View的事件分发机制详细版(如何阅读与分析源码)

        五一还过的快乐吗,是不是收假第一天感觉好累。今天为大家讲一下VIEW的事件分发过程,来**大家想要提高技术水平的欲望,这样也许就不累了吧。虽然网上有很多关于事件分发的文章,大多数思路都不是那么顺。我们做任何事情都应该从最简单的部分做起,从最简单的事情倒推其原理与过程,一步一步拼接成复杂的整体。那好,就让我们从最简单的Button点击事件说起(示例源码:https://download.csdn.net/download/gaoxiaoweiandy/11161215)。

1. 分析最简单的onClick单击事件 

    1.1 布局与JAVA代码

       activity_main.xml:

<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:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dp"
        android:text="Button" />
</RelativeLayout>

Java Code:

package com.example.event1;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
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 {

	private Button button1;
	private RelativeLayout layout;
	private ImageView imvTest;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		button1 = (Button)findViewById(R.id.button1);
		layout = (RelativeLayout)findViewById(R.id.layout);
		
		layout.setOnClickListener(this);
		button1.setOnClickListener(this);

	}

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

}

上述代码里我们为button与根布局RelativeLayout都设置了onClick监听。当我们依次点击button1与layout时,onClick函数打印日志如下:

eventTest: OnClickListener----view:android.widget.Button
eventTest: OnClickListener----view:android.widget.RelativeLayout

         这说明button与RelativeLayout的单击事件的分发过程还是有雷同的部分的,因为它们都继承于VIEW,不过还是有一些区别的,今天我们先讲解Button(View)的事件分发,至于RelativeLayout的事件分发,我们在下一篇VIEWGROUP的事件分发中讲解。Tip:在这里我们知道了事件分发的知识点可分为View事件分发与ViewGroup事件分发。由于Button,TextView,Imageview这类控件的事件分发过程其实都是由父类View的源码来完成的,所以我们把这些控件就看作VIEW.,把RelativeLayout、LinearLayout,FrameLayout都看作ViewGroup,因为它们的事件分发过程都是由直接父类ViewGroup的源码来控制。

   1.2 分析源码,整理事件分发机制(本文以onClick事件为例)

          此刻我们就来分析一下Button的onClick, android的事件是经历了一个什么过程才传递到这个onClick函数里的。我们运用“倒推”的思路来分析产生onClick调用的源头。好,那我们就看看源码中是谁调用了onClick,然后我们顺藤摸瓜,一步一步找到堆栈调用的源头函数。前面我们说了Button的事件过程是由基类View控制,那我们就来看看View的源码,在View的源码里找出是谁调用了onClick。View的源码2万多行,我来教大家如何来看源码,我们只能带着目的和思路去看源码,因为如果全看的话会陷入困惑与疲惫中。

        我们知道点击Button时调用的是onClick函数,同时会有DOWN事件与UP事件的处理才会产生单击。OK,那我们现在有2个思路,第1,在View源码里搜索  "onClick(" 注意旁边有一个左括号 ,这个的目的是用来搜索哪个地方调用了onClick函数;第2,在View源码里搜索处理DOWN事件与UP事件的代码。我们先用第1个思路,搜“onClick(”, 搜出的代码片段包括以下:

     1. DeclaredOnClickListener

Android开发笔记(三十一)Android View的事件分发机制详细版(如何阅读与分析源码)

第5623行搜到了"onClick(",它包含在DeclaredOnClickListener类里,这个是一个onClickListener的实现类,很显然我们在MainActivity.java里已经实现了onClickListener的onClick函数,所以这个DeclaredOnClickListener类应该不是我们要找的线索,不过我们还是证实一下,我们在View里再搜DeclaredOnClickListener类是用来做什么的,

Android开发笔记(三十一)Android View的事件分发机制详细版(如何阅读与分析源码)

 

 

Android开发笔记(三十一)Android View的事件分发机制详细版(如何阅读与分析源码)

        我们最终在View的构造函数里找到了DeclaredOnClickListener类,我们看到第5280行与5288行,大家能想到这是正在做什么吗?R.styleable.View_onclick:是  读取的view的onClick属性,handlerName就是onClick的值,即将来单击view时要调用的函数名。DeclaredOnClickListener的onClick里调用了我们配置的函数名handlerName(mMethodName), 看到这里我估计大家已经明白了,我们是否玩过在XML里定义为按钮定义点击函数:

    <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" />

      其中android:onClick指定了handlerName(mMethodName) 函数名为buttonClick, 直接在MainActivitry.java中去声明这个函数及实现代码即可实现单击响应处理。 这里就不再赘述了,我们明白了DeclaredOnClickListener是为了给xml里定义单击处理函数,并不是我们要找的 调用onClick的源头。OK,接下来我们看一下搜“onClick(”关键字,搜出来的代码片段2,我们逐个来辨别。

    2.  interface

    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

这个不用多说了吧,就是MainActivity.java中为按钮定义单击处理代码时,实现的那个接口类的定义。我们要找的是调用(回调)onClick函数的地方,这个只是onClick函数的抽象声明。

   3.   callOnClick函数


    /**
     * Directly call any attached OnClickListener.  Unlike {@link #performClick()},
     * this only calls the listener, and does not do any associated clicking
     * actions like reporting an accessibility event.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean callOnClick() {
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            li.mOnClickListener.onClick(this);
            return true;
        }
        return false;
    }

        这个貌似有戏,其中li.mOnClickListener.onClick(this);正是调用了我们的MainActivity里的onClick函数。但是根据注释描述:这个只是调用了onClick函数,并不是点击事件引起的,它相当于一个普通函数的调用。这个应该是用于盲人模拟手指点击事件,直接调用onClick函数,关于模拟手指点击在此不予讨论。看来这个还不是我们需要的。

   4.  performClick函数

  /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    // NOTE: other methods on View should not call this method directly, but performClickInternal()
    // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
    // could extend this method without calling super.performClick()).
    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

 

看见 if 语句 里面的 li.mOnClickListener.onClick(this) 正是调用了我们为button定义的onClick函数。其中i.mOnClickListener正是MainAcitivty.java中为button实现的那个onClickListener接口实例:button1.setOnClickListener(this);

OK,我们只搜索到了4个结果包含“onClick(”的代码片段,唯独这最后一个比较靠谱。那我们现在就来搜一下这个performClick函数在View中又是在哪里调用的? 经过在View.java源码里搜索“performClick”,发现performClickInternal调用了performClick函数,而performClickInternal函数又是在onTouchEvent里调用的,前提是控件可单击(clickable = true),  然后在case MotionEvent.ACTION_UP抬起事件下调用performClickInternal函数的。

public boolean onTouchEvent(MotionEvent event) {

   if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:

                 ......
                          if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                          }
                          if (!post(mPerformClick)) {
                                    performClickInternal();
                           }

     return true;
  }
  ......
  return false;
         
}

Ok,至此我们得到了一个重要的结论:Button(View)的onClick函数是在View的onTouchEvent里调用的。

接下来我们来看一下onTouchEvent又是谁调起的。经过搜索原来是在dispatchTouchEvent事件分发函数里调起的。dispatchTouchEvent函数的代码如下:

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        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)) {
                result = true;
            }

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

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

 

      我们从以上代码中摘取重点代码片段:      

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
    我们发现当第2个if语句条件中 result = false时 才会执行onTouchEvent(event)函数,从而才能响应单击函数。
然而,要想result = false,那么第1个if语句中,要么没有为Button设置onTouchListener,
要么设置了但是onTouch返回false。换句话,可以总结以下2个结论:
1. 没有为button设置onTouchListener, 则会执行onTouchEvent,从而onClick单击函数得以响应。

2. 为为button设置onTouchListener, 并实现了onTouch函数,让onTouch函数返回false,
同样会执行onTouchEvent,从而onClick单击函数得以响应;让onTouch返回true则不会执行onClick。

   关于第1个结论我们在1.1的代码中已经证实,确实没有设置onTouchListener,从而响应了onClick函数。

  关于第2个结论我们修改一下代码来证实,在此之前我们还是在这里先总结一下View事件分发链的过程如下:

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

最先由dispatchTouchEvent捕捉事件并分发,先执行onTouchListener的回调函数“onTouch”,然后onTouch返回false的话,就会继续往下执行onTouchEvent,onClick等。好的,我们现在修改一下代码来证明我们通过分析源码得出的结论2.

 

  1.3 证明上面的结论2

我们修改MainActivity.java代码,为button设置一个onTouchListener, 让它的回调函数onTouch分别返回false与true来看一下打印出的Log日志。

     1.3.1  onTouchListener.onTouch返回false

       修改后的MainActivity.java代码如下:

package com.example.event1;

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 Button button1;
	private RelativeLayout layout;
	private ImageView imvTest;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		button1 = (Button)findViewById(R.id.button1);
		layout = (RelativeLayout)findViewById(R.id.layout);
		
                layout.setOnTouchListener(this);
		layout.setOnClickListener(this);
		
		button1.setOnClickListener(this);
		button1.setOnTouchListener(this);
		
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
	     return false;
		
	}

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


}

 

 

我们让onTouchListener.c函数返回false, 然后当点击Button时日志如下:

eventTest: onTouchListener:acton--0----view:android.widget.Button
eventTest: onTouchListener:acton--2----view:android.widget.Button
eventTest: onTouchListener:acton--1----view:android.widget.Button
eventTest: OnClickListener----view:android.widget.Button

0代表down按下事件,2代表Move事件(可能我们在单击的时候手指颤了一下,标注的单击事件只有Down与UP),3代表UP事件。  从日志中可以看出,我们的按钮经历了Down---Move---Up事件,同时onTouchListener.onTouch返回了false,就会执行onTouchEvent,所以onClick得以响应。

同理,我们也为RelativeLayout设置了onTouchListener.onTouch与onClick,点击RelativeLayout后,日志如下:

eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout

我们发现ViewGroup与View的事件分发在此一致,只是少了个事件2(Move事件,这次手没抖,呵呵)。当然我们前面提到了ViewGroup事件分发的有些细节还是与View不一样,这个我们下一次讨论。

      1.3.2  onTouchListener.onTouch返回true

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
	       return true;
		
	}

这时我们再次点击Button,日志如下:

eventTest: onTouchListener:acton--0----view:android.widget.Button
eventTest: onTouchListener:acton--1----view:android.widget.Button

我们发现只是打印出了onTouchListener.onTouch函数中的Down与Up事件,并没有打印出OnClickListener函数的执行日志,说明当onTouch返回true时,onTouchEvent不会执行,当然也就不会调用onClick了。

Ok,整个View的单击事件(或者直接说事件,因为我们只是以单击为例来讲解)分发的一个正常流程就介绍完毕了,同时验证了onTouchListener.onTouch返回false与true时,会影响onClick的执行。但是,这还未结束。接下来我们来看一下非正常流程,我们人为的让dispatchTouchEvent返回false,返回true。或者让onTouchEvent返回false或true, 然后来看一下事件分发的过程。这里我可以说明一下,我们上面演示的正常流程下,dispatchTouchEvent与onTouchEvent默认总是返回true。我们接下来先关注一下dispatchTouchEvent返回false或true对按钮的事件分发有何用.

 

  2.  重写dispatchTouchEvent

此节我们让dispatchTouchEvent分别返回true和false,来看一下对button事件分发(接收)的影响。

      2.1 dispatchTouchEvent返回true

       为了自定义dispatchTouchEvent,我们需要自定义Button,代码如下:

package com.example.event1;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
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;
	}
	
}

同时布局文件改为:

<RelativeLayout 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.event1.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" />

 />
</RelativeLayout>

 

MainActivity.java代码不变:

package com.example.event1;

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 Button button1;
	private RelativeLayout layout;
	private ImageView imvTest;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		button1 = (Button)findViewById(R.id.button1);
		layout = (RelativeLayout)findViewById(R.id.layout);
		
                layout.setOnTouchListener(this);
		layout.setOnClickListener(this);
		
		button1.setOnClickListener(this);
		button1.setOnTouchListener(this);
		
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
	     return false;
		
	}

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


}

我们在

dispatchTouchEvent与onTouchEvent调用了super类(View类)的函数,即打印出默认返回值。
猜猜它们默认返回true还是false,在此我猜它默认都返回true.
因为我们前面的正常事件分发流程已经验证了这个。我们单击Button还是看一下日志吧:
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
eventTest: onTouchListener:acton--1----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
eventTest: OnClickListener----view:com.example.event1.MyButton

我们的猜测是对的,默认dispatchTouchEvent与onTouchEvent都返回true. 按钮也如期执行了onClick函数。

函数执行顺序是 

  • Down事件(action:0):

dispatchTouchEvent(自定义Button)----->  super.dispatchTouchEvent(View) ----> onTouchListener.onTouch(打印onTouchListener日志)---false---->onTouchEvent(自定义Button)----->super.onTouchEvent(return true)--->打印onTouchEvent日志----->打印dispatchTouchEvent日志。

  • UP事件(action:1)执行顺序同上。

为了更明显写,我们修改MyButton.java直接让dispatchTouchEvent与onTouchEvent返回true,效果是一样的。

package com.example.event1;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
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 true;

	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {

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

这里的super.dispatchTouchEvent(event)与super.onTouchEvent(event)不能省略,因为主要实现都在View源码里。

Ok,我们接下来我们让dispatchTouchEvent返回false试验下,我们猜测一下都会执行哪些事件,我反正猜测不出来,代码及打印出的日志如下:

     2.2 dispatchTouchEvent返回false

package com.example.event1;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
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);//默认返回true

                dispatchResult = false;

 
                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 true;
	}
	
}
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton

eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout

发现MyButton.dispatchTouchEvent返回false,会使Button只能接收到Down事件,后续的UP事件不再接收,同时会把DOWN事件向父布局分发,由于父布局的dispatchTouchEvent默认返回true,所以父布局RelativeLayout后续的UP事件还会继续接收,由于onTouchListener.onTouch统一返回false,所以RelativeLayout的onClick得以执行。

这里得出一个重要结论:只要View的dispatchTouchEvent在处理某一事件时返回了false(比如这里的DOWN事件),那么后续的action事件将不再由该View接收,会将DOWN事件向上传递给父容器ViewGroup。

 

   2.3 onTouchEvent返回值对dispatchTouchEvent返回值的影响

       2.3.1 onTouchEvent返回false

         Ok,我们接下来看一下把onTouchEvent返回值改为false时,对dispatchTouchEvent的返回值的影响。

        MyButton代码如下:

package com.example.event1;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
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);//默认返回true

                dispatchResult = false;

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

执行日志如下:

eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton
eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout

发现效果与2.2一样没有变化,也就是说onTouchEvent返回false还是true不起决定作用,  dispatchTouchEvent的返回false起了决定性作用。那么onTouchEvent返回false或true,在什么情况会影响dispatchTouchEvent,是在MyButton的dispatchTouchEvent返回super.dispatchTouchEvent的返回值的时候有影响:

package com.example.event1;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
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);//默认返回true

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

点击Button,我猜测这样会和上述情况一样,只有DOWN事件被接收处理,后续的UP事件将不再由Button接收。猜测为这个结果的理由是当onTouchEvent返回了false,那么super.dispatchTouchEventy也就返回了false,因此MyButton.dispatchTouchEvent返回fasle,这样就和我们上面2.2节的MyButton.dispatchTouchEvent直接返回false的效果一样了。

我们看一下日志,看猜的是否正确。点击Button日志如下:

eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton
eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout

果然,结果一样!  

 

 2.3.2 onTouchEvent返回true

这时,接着把onTouchEvent强行返回true,MyButton代码如下:

package com.example.event1;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
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);//默认返回true

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

猜测结果是:Button可以正常响应click.而且时间不会分发给ViewGroup,理由是dispatchTouchEvent将返回true,接下来的UP事件Button还会接收到的。我们看下点击Button后的日志:

eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
eventTest: onTouchListener:acton--1----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
eventTest: OnClickListener----view:com.example.event1.MyButton

结果,真的是这样,MyButton一路接收了Down与UP事件,正常执行了onClick函数。

Ok,至此View的整个事件分发流程介绍完毕,我们总结为以下两点:

  1.  View事件执行流程

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

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

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

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

 

备注:该View源码是基于API28。同时ViewGroup的事件分发将在下一篇详解。

示例源码:https://download.csdn.net/download/gaoxiaoweiandy/11161215