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:


  
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:id="@+id/layout"
  5. >
  6. <Button
  7. android:id="@+id/button1"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_marginTop="14dp"
  11. android:text="Button" />
  12. </RelativeLayout>

Java Code:


  
  1. package com.example.event1;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.widget.Button;
  8. import android.widget.ImageView;
  9. import android.widget.RelativeLayout;
  10. public class MainActivity extends Activity implements OnClickListener {
  11. private Button button1;
  12. private RelativeLayout layout;
  13. private ImageView imvTest;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. button1 = (Button)findViewById(R.id.button1);
  19. layout = (RelativeLayout)findViewById(R.id.layout);
  20. layout.setOnClickListener(this);
  21. button1.setOnClickListener(this);
  22. }
  23. @Override
  24. public void onClick(View v) {
  25. Log.i("eventTest", "OnClickListener----view:"+v);
  26. }
  27. }

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


  
  1. eventTest: OnClickListener----view:android.widget.Button
  2. 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里定义为按钮定义点击函数:


  
  1. <Button
  2. android:onClick="buttonClick"
  3. android:id="@+id/button1"
  4. android:layout_width="wrap_content"
  5. android:layout_height="wrap_content"
  6. android:layout_marginTop="14dp"
  7. android:text="Button" />

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

    2.  interface


  
  1. /**
  2. * Interface definition for a callback to be invoked when a view is clicked.
  3. */
  4. public interface OnClickListener {
  5. /**
  6. * Called when a view has been clicked.
  7. *
  8. * @param v The view that was clicked.
  9. */
  10. void onClick(View v);
  11. }

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

   3.   callOnClick函数


  
  1. /**
  2. * Directly call any attached OnClickListener. Unlike {@link #performClick()},
  3. * this only calls the listener, and does not do any associated clicking
  4. * actions like reporting an accessibility event.
  5. *
  6. * @return True there was an assigned OnClickListener that was called, false
  7. * otherwise is returned.
  8. */
  9. public boolean callOnClick() {
  10. ListenerInfo li = mListenerInfo;
  11. if (li != null && li.mOnClickListener != null) {
  12. li.mOnClickListener.onClick(this);
  13. return true;
  14. }
  15. return false;
  16. }

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

   4.  performClick函数


  
  1. /**
  2. * Call this view's OnClickListener, if it is defined. Performs all normal
  3. * actions associated with clicking: reporting accessibility event, playing
  4. * a sound, etc.
  5. *
  6. * @return True there was an assigned OnClickListener that was called, false
  7. * otherwise is returned.
  8. */
  9. // NOTE: other methods on View should not call this method directly, but performClickInternal()
  10. // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
  11. // could extend this method without calling super.performClick()).
  12. public boolean performClick() {
  13. // We still need to call this method to handle the cases where performClick() was called
  14. // externally, instead of through performClickInternal()
  15. notifyAutofillManagerOnClick();
  16. final boolean result;
  17. final ListenerInfo li = mListenerInfo;
  18. if (li != null && li.mOnClickListener != null) {
  19. playSoundEffect(SoundEffectConstants.CLICK);
  20. li.mOnClickListener.onClick(this);
  21. result = true;
  22. } else {
  23. result = false;
  24. }
  25. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  26. notifyEnterOrExitForAutoFillIfNeeded(true);
  27. return result;
  28. }

 

看见 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函数的。


  
  1. public boolean onTouchEvent(MotionEvent event) {
  2. if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
  3. switch (action) {
  4. case MotionEvent.ACTION_UP:
  5. ......
  6. if (mPerformClick == null) {
  7. mPerformClick = new PerformClick();
  8. }
  9. if (!post(mPerformClick)) {
  10. performClickInternal();
  11. }
  12. return true;
  13. }
  14. ......
  15. return false;
  16. }

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

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


  
  1. /**
  2. * Pass the touch screen motion event down to the target view, or this
  3. * view if it is the target.
  4. *
  5. * @param event The motion event to be dispatched.
  6. * @return True if the event was handled by the view, false otherwise.
  7. */
  8. public boolean dispatchTouchEvent(MotionEvent event) {
  9. // If the event should be handled by accessibility focus first.
  10. if (event.isTargetAccessibilityFocus()) {
  11. // We don't have focus or no virtual descendant has it, do not handle the event.
  12. if (!isAccessibilityFocusedViewOrHost()) {
  13. return false;
  14. }
  15. // We have focus and got the event, then use normal event dispatch.
  16. event.setTargetAccessibilityFocus(false);
  17. }
  18. boolean result = false;
  19. if (mInputEventConsistencyVerifier != null) {
  20. mInputEventConsistencyVerifier.onTouchEvent(event, 0);
  21. }
  22. final int actionMasked = event.getActionMasked();
  23. if (actionMasked == MotionEvent.ACTION_DOWN) {
  24. // Defensive cleanup for new gesture
  25. stopNestedScroll();
  26. }
  27. if (onFilterTouchEventForSecurity(event)) {
  28. if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
  29. result = true;
  30. }
  31. //noinspection SimplifiableIfStatement
  32. ListenerInfo li = mListenerInfo;
  33. if (li != null && li.mOnTouchListener != null
  34. && (mViewFlags & ENABLED_MASK) == ENABLED
  35. && li.mOnTouchListener.onTouch(this, event)) {
  36. result = true;
  37. }
  38. if (!result && onTouchEvent(event)) {
  39. result = true;
  40. }
  41. }
  42. if (!result && mInputEventConsistencyVerifier != null) {
  43. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  44. }
  45. // Clean up after nested scrolls if this is the end of a gesture;
  46. // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
  47. // of the gesture.
  48. if (actionMasked == MotionEvent.ACTION_UP ||
  49. actionMasked == MotionEvent.ACTION_CANCEL ||
  50. (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
  51. stopNestedScroll();
  52. }
  53. return result;
  54. }

 

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


  
  1. ListenerInfo li = mListenerInfo;
  2. if (li != null && li.mOnTouchListener != null
  3. && (mViewFlags & ENABLED_MASK) == ENABLED
  4. && li.mOnTouchListener.onTouch(this, event)) {
  5. result = true;
  6. }
  7. if (!result && onTouchEvent(event)) {
  8. result = true;
  9. }

  
  1. 我们发现当第2个if语句条件中 result = false时 才会执行onTouchEvent(event)函数,从而才能响应单击函数。
  2. 然而,要想result = false,那么第1个if语句中,要么没有为Button设置onTouchListener,
  3. 要么设置了但是onTouch返回false。换句话,可以总结以下2个结论:
  1. 1. 没有为button设置onTouchListener, 则会执行onTouchEvent,从而onClick单击函数得以响应。
  2. 2. 为为button设置onTouchListener, 并实现了onTouch函数,让onTouch函数返回false,
  3. 同样会执行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代码如下:


  
  1. package com.example.event1;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.Button;
  9. import android.widget.ImageView;
  10. import android.widget.RelativeLayout;
  11. public class MainActivity extends Activity implements OnClickListener, View.OnTouchListener {
  12. private Button button1;
  13. private RelativeLayout layout;
  14. private ImageView imvTest;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. button1 = (Button)findViewById(R.id.button1);
  20. layout = (RelativeLayout)findViewById(R.id.layout);
  21. layout.setOnTouchListener(this);
  22. layout.setOnClickListener(this);
  23. button1.setOnClickListener(this);
  24. button1.setOnTouchListener(this);
  25. }
  26. @Override
  27. public boolean onTouch(View v, MotionEvent event) {
  28. Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
  29. return false;
  30. }
  31. @Override
  32. public void onClick(View v) {
  33. Log.i("eventTest", "OnClickListener----view:"+v);
  34. }
  35. }

 

 

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


  
  1. eventTest: onTouchListener:acton--0----view:android.widget.Button
  2. eventTest: onTouchListener:acton--2----view:android.widget.Button
  3. eventTest: onTouchListener:acton--1----view:android.widget.Button
  4. 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后,日志如下:


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

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

      1.3.2  onTouchListener.onTouch返回true


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

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


  
  1. eventTest: onTouchListener:acton--0----view:android.widget.Button
  2. 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,代码如下:


  
  1. package com.example.event1;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.WindowInsets;
  7. import android.widget.Button;
  8. public class MyButton extends Button {
  9. public MyButton(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean dispatchTouchEvent(MotionEvent event) {
  15. boolean dispatchResult = super.dispatchTouchEvent(event);
  16. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  17. ",result="+dispatchResult+",view:MyButton");
  18. return dispatchResult;
  19. }
  20. @Override
  21. public boolean onTouchEvent(MotionEvent event) {
  22. boolean onTouchEventResult = super.onTouchEvent(event); //这个默认返回true
  23. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  24. ",result="+onTouchEventResult+",view:MyButton");
  25. return onTouchEventResult;
  26. }
  27. }

同时布局文件改为:


  
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:id="@+id/layout"
  5. >
  6. <com.example.event1.MyButton
  7. android:onClick="buttonClick"
  8. android:id="@+id/button1"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_marginTop="14dp"
  12. android:text="Button" />
  13. />
  14. </RelativeLayout>

 

MainActivity.java代码不变:


  
  1. package com.example.event1;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.Button;
  9. import android.widget.ImageView;
  10. import android.widget.RelativeLayout;
  11. public class MainActivity extends Activity implements OnClickListener, View.OnTouchListener {
  12. private Button button1;
  13. private RelativeLayout layout;
  14. private ImageView imvTest;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. button1 = (Button)findViewById(R.id.button1);
  20. layout = (RelativeLayout)findViewById(R.id.layout);
  21. layout.setOnTouchListener(this);
  22. layout.setOnClickListener(this);
  23. button1.setOnClickListener(this);
  24. button1.setOnTouchListener(this);
  25. }
  26. @Override
  27. public boolean onTouch(View v, MotionEvent event) {
  28. Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
  29. return false;
  30. }
  31. @Override
  32. public void onClick(View v) {
  33. Log.i("eventTest", "OnClickListener----view:"+v);
  34. }
  35. }

我们在


  
  1. dispatchTouchEvent与onTouchEvent调用了super类(View类)的函数,即打印出默认返回值。
  2. 猜猜它们默认返回true还是false,在此我猜它默认都返回true.
  3. 因为我们前面的正常事件分发流程已经验证了这个。我们单击Button还是看一下日志吧:

  
  1. eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
  2. eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
  3. eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
  4. eventTest: onTouchListener:acton--1----view:com.example.event1.MyButton
  5. eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
  6. eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
  7. 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,效果是一样的。


  
  1. package com.example.event1;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.WindowInsets;
  7. import android.widget.Button;
  8. public class MyButton extends Button {
  9. public MyButton(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean dispatchTouchEvent(MotionEvent event) {
  15. boolean dispatchResult = super.dispatchTouchEvent(event);
  16. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  17. ",result="+dispatchResult+",view:MyButton");
  18. return true;
  19. }
  20. @Override
  21. public boolean onTouchEvent(MotionEvent event) {
  22. boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
  23. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  24. ",result="+onTouchEventResult+",view:MyButton");
  25. return true;
  26. }
  27. }

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

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

     2.2 dispatchTouchEvent返回false


  
  1. package com.example.event1;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.WindowInsets;
  7. import android.widget.Button;
  8. public class MyButton extends Button {
  9. public MyButton(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean dispatchTouchEvent(MotionEvent event) {
  15. boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
  16. dispatchResult = false;
  17. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  18. ",result="+dispatchResult+",view:MyButton");
  19. return dispatchResult;
  20. }
  21. @Override
  22. public boolean onTouchEvent(MotionEvent event) {
  23. boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
  24. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  25. ",result="+onTouchEventResult+",view:MyButton");
  26. return true;
  27. }
  28. }

  
  1. eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
  2. eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
  3. eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton
  4. eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
  5. eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
  6. 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代码如下:


  
  1. package com.example.event1;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.WindowInsets;
  7. import android.widget.Button;
  8. public class MyButton extends Button {
  9. public MyButton(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean dispatchTouchEvent(MotionEvent event) {
  15. boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
  16. dispatchResult = false;
  17. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  18. ",result="+dispatchResult+",view:MyButton");
  19. return dispatchResult;
  20. }
  21. @Override
  22. public boolean onTouchEvent(MotionEvent event) {
  23. boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
  24. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  25. ",result="+false+",view:MyButton");
  26. return false;
  27. }
  28. }

执行日志如下:


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

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


  
  1. package com.example.event1;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.WindowInsets;
  7. import android.widget.Button;
  8. public class MyButton extends Button {
  9. public MyButton(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean dispatchTouchEvent(MotionEvent event) {
  15. boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
  16. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  17. ",result="+dispatchResult+",view:MyButton");
  18. return dispatchResult;
  19. }
  20. @Override
  21. public boolean onTouchEvent(MotionEvent event) {
  22. boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
  23. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  24. ",result="+false+",view:MyButton");
  25. return false;
  26. }
  27. }

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

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


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

果然,结果一样!  

 

 2.3.2 onTouchEvent返回true

这时,接着把onTouchEvent强行返回true,一般情况下这个函数默认返回true(super.onTouchEvent(event)默认返回true,只要控件是可点击的或者为控件设置了onClickListener),MyButton代码如下:


  
  1. package com.example.event1;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.MotionEvent;
  6. import android.view.WindowInsets;
  7. import android.widget.Button;
  8. public class MyButton extends Button {
  9. public MyButton(Context context, AttributeSet attrs) {
  10. super(context, attrs);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean dispatchTouchEvent(MotionEvent event) {
  15. boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
  16. Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
  17. ",result="+dispatchResult+",view:MyButton");
  18. return dispatchResult;
  19. }
  20. @Override
  21. public boolean onTouchEvent(MotionEvent event) {
  22. boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
  23. Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
  24. ",result="+onTouchEventResult+",view:MyButton");
  25. return true;
  26. }
  27. }

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


  
  1. eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
  2. eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
  3. eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
  4. eventTest: onTouchListener:acton--1----view:com.example.event1.MyButton
  5. eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
  6. eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
  7. 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个月内不可修改。