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

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

        五一还过的快乐吗,是不是收假第一天感觉好累。今天为大家讲一下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

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

 

 

        我们最终在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.onTouch函数返回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),1代表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="+false+",view:MyButton");
     		return false;
      	}
      }
  
 

执行日志如下:


      eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
      eventTest: MyonTouchEvent:action--0,result=false,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="+false+",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=false,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,一般情况下这个函数默认返回true(super.onTouchEvent(event)默认返回true,只要控件是可点击的或者为控件设置了onClickListener),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

 

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/89840433

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。