Android 事件传递机制

作者: InFatuated | 来源:发表于2020-05-15 11:16 被阅读0次

一、事件分发的原理

1、事件是如何传递的:
  • 首先由Activity分发,分发给根View,也就是DecorView(DecorView为整个Window界面的最顶层View)
  • 然后由根View分发到子的View
    如图:

    以及整个事件传递机制的核心
    如图:

    上图显示:
    在ViewGroup中可以通过onInterceptTouchEvent方法对时间传递进行拦截。onInterceptTouchEvent方法:
    返回ture代表不允许时间继续向子View传递,将会触发当前View的onTouchEvent(),进行事件的消费;
    返回false代表不对时间进行拦截,时间可以传递给孩子
    默认返回false
2、时间是如何处理的

如图:



从图中可看出:子View中如果将传递的事件消费掉,父类的ViewGroup中将无法接受到任何事件

二、onTouch和onClick时间同时发生的问题:

首先这里要解释一下各种概念,避免混淆。

1、各种概念
  • 事件:
    混合体(可能是点击事件也可能是是触摸事件 )
  • 触摸事件:
    按下、滑动和离开
  • 点击事件:
    按下、停留一会儿和离开
  • 触摸onTouch时间和点击onClick事件有什么关系?
    1、执行先后不一样。触摸事件先执行
    2、触摸事件返回值影响点击事件(前者影响后者,而后者不影响前者)
2、onTouch和onClick事件同时执行:

如果按钮的onTouch和onClick方法同时执行,会有什么效果呢?我们通过代码来看一下:

1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.util.Log;
 4 import android.view.MotionEvent;
 5 import android.view.View;
 6 import android.widget.Button;
 7 
 8 public class MainActivity extends Activity {
 9 
10     private static final String TAG = "MainActivity";
11     private Button btn;
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.activity_main);
17         btn = (Button) findViewById(R.id.btn);
18 
19         //按钮的touch触摸事件
20         btn.setOnTouchListener(new View.OnTouchListener() {
21             @Override
22             public boolean onTouch(View v, MotionEvent event) {
23                 switch (event.getAction()) {
24                     case MotionEvent.ACTION_DOWN: //按下的动作
25                         Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");
26                         break;
27                     case MotionEvent.ACTION_MOVE: //滑动的动作
28                         Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");
29                         break;
30                     case MotionEvent.ACTION_UP: //离开的动作
31                         Log.d(TAG, "btn is MotionEvent.ACTION_UP");
32                         break;
33                 }
34 
35                 return false;  //默认的返回值
36             }
37         });
38 
39         //按钮的点击事件
40         btn.setOnClickListener(new View.OnClickListener() {
41             @Override
42             public void onClick(View v) {
43                 Log.d(TAG, "btn is click");
44             }
45         });
46     }
47 
48 }

上方代码中,按钮btn既包含了onTouch事件,也包含了onClick事件,现在运行程序,点击按钮,后台打印日志如下:



显然,通过日志可以看出,onTouch时间是比onClick事件先执行的。
备注:这里需要提醒一下,如果我们仅仅只是用手指点击按钮,然后马上松开,onTouch时间中只会执行ACTION_DOWN和ACTION_UP动作;如果用手机点击按钮,并且手指还在按钮上滑动了一会,那么滑动过程中,ACTION_MOVE动作就会不停的执行。

3、只执行onTouch事件,不执行onClick事件:

如果按钮的onTouch和onClick方法同时执行,在有些情况下不太满足产品的需求。那如果只想执行onTouch事件,不执行onClick事件,该怎么做呢?很简单,只需要在上方代码中,即onTouch方法返回值设为true,就会只执行onTouch事件,不执行onCLick事件。改完代码,可看到如下日志:



为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。
button按钮中没有dispatchTouchEvent方法,需要去它的父类View.java中去找dispatchTouchEvent方法。源码如下:



红框部分的onTouch()方法默认返回为false,所以会执行篮框部分代码,即调用onTouchEvent()。而onTouchEvent方法中,会在ACTION_UP动作里初始化onClick时间。
如图

于是最后,onClick事件就会得到执行。

3、onClick和onLongClick时间同时发生:

这里我们同样通过代码演示一下。

1、onTouch、onLongClick、onClick时间默认是同时执行:执行的先后顺序:onTouch>onLongClick>onClick

完整代码如下:

 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.util.Log;
 4 import android.view.MotionEvent;
 5 import android.view.View;
 6 import android.widget.Button;
 7 
 8 public class MainActivity extends Activity {
 9 
10     private static final String TAG = "MainActivity";
11     private Button btn;
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.activity_main);
17         btn = (Button) findViewById(R.id.btn);
18 
19         //按钮的touch事件
20         btn.setOnTouchListener(new View.OnTouchListener() {
21             @Override
22             public boolean onTouch(View v, MotionEvent event) {
23 
24                 switch (event.getAction()) {
25                     case MotionEvent.ACTION_DOWN: //按下的动作
26                         Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");
27                         break;
28 
29                     case MotionEvent.ACTION_MOVE: //滑动的动作
30                         Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");
31                         break;
32 
33                     case MotionEvent.ACTION_UP: //离开的动作
34                         Log.d(TAG, "btn is MotionEvent.ACTION_UP");
35                         break;
36                 }
37 
38                 return false;  //默认的返回值
39             }
40         });
41 
42 
43         //按钮的onLongClick事件
44         btn.setOnLongClickListener(new View.OnLongClickListener() {
45             @Override
46             public boolean onLongClick(View v) {
47 
48                 Log.d(TAG, "btn is onLongClick");
49 
50                 return false; //默认的返回值
51             }
52         });
53         //按钮的onClick事件
54         btn.setOnClickListener(new View.OnClickListener() {
55             @Override
56             public void onClick(View v) {
57                 Log.d(TAG, "btn is onClick");
58             }
59         });
60     }
61 
62 }

运行后日志:


同样,查看源码得知,当onTouch时间中ACTION_DOWN动作执行180ms之后,就会执行onLongClick事件。
此时ACTION_UP事件还并未发生,所以如果我们一个按钮上按下的时间过长,onLongClick事件就会比onClick事件先执行。
2、只执行onTouch事件和onLongClick事件,不执行onClick事件:

为了实现这种逻辑,只需在上方代码中,将onLongClick方法的返回值改为true,就不会执行onClick时间了。
日志如下:


从源码角度理解,在View.java中dispatchTouchEVent方法里,ACTION_UP动作里面对onLongTouch事件进行了处理。

四、事件传递机制调用顺序:

ViewGroup的事件传递方法:

  • dispatchTouchEvent
  • onInterceptTouchEvent
  • onTouchEvent
    view的事件传递方法:
    -View的dispatchTouchEvent
    -View的onTouchEvent
    注意,只有父的ViewGroup容器才有onInterceptTouchEvent()方法、这也很好理解,最小的那个子的view没必要再拦截了,因为无法继续向下传递事件,是否拦截已经没有意义。

五、简单总结一下:

  • 在android中,一切的事件处理开始都是从ACTION_DOWN事件开始,如果你处理了ACTION_DOWN事件,其他的事件便自然收不到了。
    -事件是ViewGorup向下传递给子View的,子View如果不将其消费,它会依次从内向外的向外传递事件。
  • 如果ViewGroup将事件拦截,它便不会再传递个子View,它将自己将时间给消费掉。

相关文章

网友评论

    本文标题:Android 事件传递机制

    本文链接:https://www.haomeiwen.com/subject/vgbqohtx.html