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

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

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

 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,以下:ide

 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)部署程序到手机上,以下:布局

 

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

 

从上面打印的结果分析:测试

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

dispatchTouchEvent --> onTouch --> onTouchEvent --> onClickspa

相信细心的你确定发现了,都是在ACTION_UP事件以后才触发onClick点击事件3d

 

 

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事件
相关文章
相关标签/搜索