自定义控件(视图)2期笔记14:自定义视图之View事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

1. 这里我们先从案例角度说明dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

(1)首先我们重写一个MyButton 继承自 Button,代码如下:

 1 package com.himi.eventdemo;
 2 
 3 import android.content.Context;
 4 import android.util.AttributeSet;
 5 import android.util.Log;
 6 import android.view.MotionEvent;
 7 import android.widget.Button;
 8 
 9 public class MyButton extends Button {
10     
11     private static String TAG = "MyButton";
12     public MyButton(Context context) {
13         super(context);
14     }
15 
16     public MyButton(Context context, AttributeSet attrs) {
17         super(context, attrs);
18     }
19 
20     public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
21         super(context, attrs, defStyleAttr);
22     }
23 
24 
25     @Override
26     public boolean dispatchTouchEvent(MotionEvent event) {
27 
28         switch (event.getAction()) {
29             case MotionEvent.ACTION_DOWN:
30                 Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_DOWN");
31                 break;
32             case MotionEvent.ACTION_MOVE:
33                  Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_MOVE");
34                 break;
35             case MotionEvent.ACTION_UP:
36                  Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_UP");
37                 break;
38         }
39 
40         return super.dispatchTouchEvent(event);
41     }
42 
43     @Override
44     public boolean onTouchEvent(MotionEvent event) {
45 
46         switch (event.getAction()) {
47             case MotionEvent.ACTION_DOWN:
48                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_DOWN");
49                 break;
50             case MotionEvent.ACTION_MOVE:
51                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_MOVE");
52                 break;
53             case MotionEvent.ACTION_UP:
54                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_UP");
55                 break;
56         }
57 
58         return super.onTouchEvent(event);
59     }
60 }

(2)来到主布局文件activity_main.xml,如下:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:gravity="center"
 6     tools:context="com.himi.eventdemo.MainActivity" >
 7     
 8     <com.himi.eventdemo.MyButton
 9         android:id="@+id/myButton"
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content"
12         android:text="测试"
13         />
14         
15 </RelativeLayout>

 

(3)测试MainActivity,如下:

 1 package com.himi.eventdemo;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.util.Log;
 6 import android.view.MotionEvent;
 7 import android.view.View;
 8 import android.widget.Button;
 9 
10 public class MainActivity extends Activity {
11     
12     private static String TAG ="MainActivity";
13 
14 
15     private Button myButton;
16 
17 
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22 
23         myButton = (Button) findViewById(R.id.myButton);
24 
25         myButton.setOnTouchListener(new View.OnTouchListener() {
26             @Override
27             public boolean onTouch(View v, MotionEvent event) {
28                 switch (event.getAction()) {
29                     case MotionEvent.ACTION_DOWN:
30                          Log.e(TAG,"onTouch====MyButton=====ACTION_DOWN");
31                         break;
32                     case MotionEvent.ACTION_MOVE:
33                          Log.e(TAG,"onTouch====MyButton=====ACTION_MOVE");
34                         break;
35                     case MotionEvent.ACTION_UP:
36                          Log.e(TAG,"onTouch====MyButton=====ACTION_UP");
37                         break;
38                 }
39                 return false;
40             }
41         });
42 
43         myButton.setOnClickListener(new View.OnClickListener() {
44             @Override
45             public void onClick(View v) {
46                 Log.e(TAG,"onClick====MyButton=====onClick");
47             }
48         });
49 
50 
51     }
52 
53 
54 }

 

(4)部署程序到手机上,如下:

 

点击测试按钮,打印结果如下:

 

从上面打印的结果分析:

点击Button按钮事件分发过程如下:

dispatchTouchEvent --> onTouch --> onTouchEvent --> onClick

相信细心的你肯定发现了,都是在ACTION_UP事件之后才触发onClick点击事件

 

 

2. 下面我们从源码的角度分析dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

(1)事件分发都是从dispatchTouchEvent方法开始的,那么我们这里是重写了dispatchTouchEvent方法,并且最后也调用了父类的super.dispatchTouchEvent(event)方法。那么我们看看父类中的方法到底做了什么??点击进入父类的dispatchTouchEvent方法,发现此方法在View类中找到,其实也不奇怪,所有控件的父类都是View。这里我贴出最新源码如下:

 1 public boolean dispatchTouchEvent(MotionEvent event) {
 2         boolean result = false;
 3 
 4         if (mInputEventConsistencyVerifier != null) {
 5             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
 6         }
 7 
 8         final int actionMasked = event.getActionMasked();
 9         if (actionMasked == MotionEvent.ACTION_DOWN) {
10             // Defensive cleanup for new gesture
11             stopNestedScroll();
12         }
13 
14         if (onFilterTouchEventForSecurity(event)) {
15             //noinspection SimplifiableIfStatement
16             ListenerInfo li = mListenerInfo;
17             if (li != null && li.mOnTouchListener != null
18                     && (mViewFlags & ENABLED_MASK) == ENABLED
19                     && li.mOnTouchListener.onTouch(this, event)) {
20                 result = true;
21             }
22 
23             if (!result && onTouchEvent(event)) {
24                 result = true;
25             }
26         }
27 
28         if (!result && mInputEventConsistencyVerifier != null) {
29             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
30         }
31 
32         // Clean up after nested scrolls if this is the end of a gesture;
33         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
34         // of the gesture.
35         if (actionMasked == MotionEvent.ACTION_UP ||
36                 actionMasked == MotionEvent.ACTION_CANCEL ||
37                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
38             stopNestedScroll();
39         }
40 
41         return result;
42     }

忽略其他无关代码,我们直接看17--25行。

第17行的 if 判断关键在于li.mOnTouchListener.onTouch(this, event) 的返回值

这个接口回调就是我们外面写的myButton.setOnTouchListener事件(Button 的onTouch事件),

在MainActivity代码里,我们setOnTouchListener返回的值是false,所以在源码中我们可以看到 17行的条件不成立,那么条件不成立,result=false;

因此,源码的第23行 if 判断第一个条件成立,继续执行第二个条件,也就是onTouchEvent。我们跳到这个方法里看看里面干啥了?看如下代码:

 1 public boolean onTouchEvent(MotionEvent event) {
 2 
 3         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 4                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 5             switch (event.getAction()) {
 6                 case MotionEvent.ACTION_UP:
 7                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
 8                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
 9                         // take focus if we don't have it already and we should in
10                         // touch mode.
11                         boolean focusTaken = false;
12                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
13                             focusTaken = requestFocus();
14                         }
15 
16                         if (prepressed) {
17                             // The button is being released before we actually
18                             // showed it as pressed.  Make it show the pressed
19                             // state now (before scheduling the click) to ensure
20                             // the user sees it.
21                             setPressed(true, x, y);
22                        }
23 
24                         if (!mHasPerformedLongPress) {
25                             // This is a tap, so remove the longpress check
26                             removeLongPressCallback();
27 
28                             // Only perform take click actions if we were in the pressed state
29                             if (!focusTaken) {
30                                 // Use a Runnable and post this rather than calling
31                                 // performClick directly. This lets other visual state
32                                 // of the view update before click actions start.
33                                 if (mPerformClick == null) {
34                                     mPerformClick = new PerformClick();
35                                 }
36                                 if (!post(mPerformClick)) {
37                      performClick();
38                                 }
39                             }
40                         }
41 
42                         if (mUnsetPressedState == null) {
43                             mUnsetPressedState = new UnsetPressedState();
44                         }
45 
46                         if (prepressed) {
47                             postDelayed(mUnsetPressedState,
48                                     ViewConfiguration.getPressedStateDuration());
49                         } else if (!post(mUnsetPressedState)) {
50                             // If the post failed, unpress right now
51                             mUnsetPressedState.run();
52                         }
53 
54                         removeTapCallback();
55                     }
56                     break;
57             return true;
58         }
59 
60         return false;
61     }

我们看看这里边都做了些什么,忽略其他,我们直接看37行的 performClick(); 方法,跳进去继续看:

(注意:这里的performClick方法是在ACTION_UP手势里边执行的哦!!!

 1 public boolean performClick() {
 2         final boolean result;
 3         final ListenerInfo li = mListenerInfo;
 4         if (li != null && li.mOnClickListener != null) {
 5             playSoundEffect(SoundEffectConstants.CLICK);
 6             li.mOnClickListener.onClick(this);
 7             result = true;
 8         } else {
 9             result = false;
10         }
11 
12         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
13         return result;
14     }

看见没??

第6行 li.mOnClickListener.onClick(this);

这个接口回调就是我们Button的 onClick事件。到此为止,我们从源码分析了Button事件分发过程。


结论:

dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。并且如果仔细的你会发现,在onTouchEvent方法内部判断执行onClick方法,但是,在所有ACTION_UP事件之后才触发onClick点击事件

 

3. 现在我们来看看其他情况:当onTouch返回为true,打印结果如下:

 1 package com.himi.eventdemo;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.util.Log;
 6 import android.view.MotionEvent;
 7 import android.view.View;
 8 import android.widget.Button;
 9 
10 public class MainActivity extends Activity {
11     
12     private static String TAG ="MainActivity";
13 
14 
15     private Button myButton;
16 
17 
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22 
23         myButton = (Button) findViewById(R.id.myButton);
24 
25         myButton.setOnTouchListener(new View.OnTouchListener() {
26             @Override
27             public boolean onTouch(View v, MotionEvent event) {
28                 switch (event.getAction()) {
29                     case MotionEvent.ACTION_DOWN:
30                          Log.e(TAG,"onTouch====MyButton=====ACTION_DOWN");
31                         break;
32                     case MotionEvent.ACTION_MOVE:
33                          Log.e(TAG,"onTouch====MyButton=====ACTION_MOVE");
34                         break;
35                     case MotionEvent.ACTION_UP:
36                          Log.e(TAG,"onTouch====MyButton=====ACTION_UP");
37                         break;
38                 }
39                 return true;
40             }
41         });
42 
43         myButton.setOnClickListener(new View.OnClickListener() {
44             @Override
45             public void onClick(View v) {
46                 Log.e(TAG,"onClick====MyButton=====onClick");
47             }
48         });
49 
50 
51     }
52 
53 
54 }

打印结果如下:

 结论:

dispatchTouchEvent---->onTouch

 

惊奇的发现,竟然没有执行onClick事件是吧????如果你仔细阅读上面的文章,估计你知道为什么了吧?还是跟大家一起分析一下吧:源码如下:

 1 public boolean dispatchTouchEvent(MotionEvent event) {
 2         boolean result = false;
 3 
 4         if (mInputEventConsistencyVerifier != null) {
 5             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
 6         }
 7 
 8         final int actionMasked = event.getActionMasked();
 9         if (actionMasked == MotionEvent.ACTION_DOWN) {
10             // Defensive cleanup for new gesture
11             stopNestedScroll();
12         }
13 
14         if (onFilterTouchEventForSecurity(event)) {
15             //noinspection SimplifiableIfStatement
16             ListenerInfo li = mListenerInfo;
17             if (li != null && li.mOnTouchListener != null
18                     && (mViewFlags & ENABLED_MASK) == ENABLED
19                     && li.mOnTouchListener.onTouch(this, event)) {
20                 result = true;
21             }
22 
23             if (!result && onTouchEvent(event)) {
24                 result = true;
25             }
26         }
27 
28         if (!result && mInputEventConsistencyVerifier != null) {
29             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
30         }
31 
32         // Clean up after nested scrolls if this is the end of a gesture;
33         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
34         // of the gesture.
35         if (actionMasked == MotionEvent.ACTION_UP ||
36                 actionMasked == MotionEvent.ACTION_CANCEL ||
37                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
38             stopNestedScroll();
39         }
40 
41         return result;
42     }

从第 17 行可以看出,条件成立,result=true;

那么第 23 行 if 条件根本不会执行第二个判断,那么就不会执行onTouchEvent方法,也就不会调用 onClick的接口,因此Button 不会执行setOnClickListener中的onClick事件。

 

4. 总结:

 

 

那么我们继续回归dispatchTouchEvent中不是ViewGroup的情形
接下来,系统会自动判断我们是否实现了onTouchListener 这里就开始有分支了
--1>. 当我们实现了onTouchListener,那么下一步我们的事件叫交给了onTouchListener.onTouch来处理,这里就又开始了分支
(1)如果我们在onTouch中返回了true,那么就表明我们的onTouchListener 已经消化掉了本次的事件,本次事件完结。这就是为什么我们在onTouch中返回去就永运不会执行onClick,onLongClick了
(2)如果我们在onTouch中返回了false,那么很明显了我们的事件就会被onTouchEvent处理
--2>. 同理,当我们没有实现了onTouchListener,很明显了我们的事件就会被onTouchEvent处理。
殊途同归,最终如果我们的事件没有被干掉,最终都交给了onTouchEvent。那么接下来我们继续来看onTouchEvent,那么我们的onTouchEvent又是用来干什么的呢(这里既然已经有onTouchListener了,他们似乎一模一样啊)?
其实不然,说白了我们的onTouchEvent最终会用来分发onClickonLongClick事件
 

 

posted on 2016-07-29 16:21  鸿钧老祖  阅读(1894)  评论(0编辑  收藏  举报

导航