(笔记:书源--Android高级进阶 顾浩鑫著 中国工信出版社)
开发时,会遇到多个View、ViewGroup嵌套的问题,此为介绍Activity、View、ViewGroup三者触摸事件传递机制。一次完整的事件传递主要包括三个阶段,分别为事件的分发、拦截和消费。
1.触摸事件的类型
MotionEvent类:
ACTION_DOWN:用户手指按下操作,一个按下操作标志着一个触摸事件的开始。(一次屏幕触摸操作的必需)
ACTION_MOVE:用户手指按压屏幕后,在松开之前,移动的距离超过一定的阈值,则被判断为此事件类型。一般情况下,手指的轻微移动都会触发一系列的移动事件。
ACTION_UP:用户手指离开屏幕的操作,一次抬起操作标志着一次触摸事件的结束。(一次屏幕触摸操作的必需)
2.事件传递的三个阶段
1)分发Dispatch-->dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
根据当前视图的具体实现逻辑,来决定是直接消费这个事件还是将事件继续分发给子视图处理,方法返回值为true表示事件被当前视图消费掉,不再分发事件;返回值为super.dispatchTouchEvent表示继续分发该事件。
如果当前视图是ViewGroup及子类,则调用OnInterceptTouchEvent方法判定是否拦截该事件。
2)拦截Intercept-->onInterceptTouchEvent
该方法旨在ViewGroup及其子类中存在,在View和Activity中是不存在的。
public boolean onInterceptTouchEvent(MotionEvent event)
也是通过返回值来决定是否拦截对应的事件,根据具体的实现逻辑,返回true表示拦截这个事件,不继续分发给子视图,同时交由自身的onTouchEvent方法进行消费;返回false或者super.onInterceptTouchEvent表示不对事件进行拦截,需要继续传递给子视图。
3)消费Consume-->onTouchEvent
public boolean onTouchEvent(MotionEvent event)
返回值为true时表示当前视图可以处理对应的事件,事件将不会向上传递给父视图;返回值为false表示当前视图不处理这个事件,事件会被传递给父视图的onTouchEvent方法进行处理。
Android系统中,拥有事件传递处理能力的类有以下三种:
Activity: 拥有dispatchTouchEvent , onTouchEvent
ViewGroup: 拥有dispatchTouchEvent , onInterceptTouchEvent , onTouchEvent
View: 拥有 dispatchTouchEvent , onTouchEvent
3.View事件传递机制
这里所说的View专指除ViewGroup外的View控件
(在这里书上继承TextView,不过AS会报错,需要改成AppCompatTextView)
1 import android.content.Context; 2 import android.support.v7.widget.AppCompatTextView; 3 import android.util.AttributeSet; 4 import android.util.Log; 5 import android.view.MotionEvent; 6 7 /** 8 * 将每个事件的触发都打印日志,方便了解事件传递的流程 9 * */ 10 public class MyTextView extends AppCompatTextView { 11 private static final String TAG="MyTextView"; 12 13 public MyTextView(Context context) { 14 super(context); 15 } 16 17 public MyTextView(Context context, AttributeSet attrs){ 18 super(context,attrs); 19 } 20 21 public boolean dispatchTouchEvent(MotionEvent ev){ 22 switch (ev.getAction()){ 23 case MotionEvent.ACTION_DOWN: 24 Log.e(TAG,"dispatchTouchEvent ACTION_DOWN"); 25 break; 26 case MotionEvent.ACTION_MOVE: 27 Log.e(TAG,"dispatchTouchEvent ACTION_MOVE"); 28 break; 29 case MotionEvent.ACTION_UP: 30 Log.e(TAG,"dispatchTouchEvent ACTION_UP"); 31 break; 32 case MotionEvent.ACTION_CANCEL: 33 Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL"); 34 break; 35 default: 36 break; 37 } 38 return super.dispatchTouchEvent(ev); 39 } 40 41 42 public boolean onTouchEvent(MotionEvent event){ 43 switch(event.getAction()){ 44 case MotionEvent.ACTION_DOWN: 45 Log.e(TAG,"onTouchEvent ACTION_DOWN"); 46 break; 47 case MotionEvent.ACTION_MOVE: 48 Log.e(TAG,"onTouchEvent ACTION_MOVE"); 49 break; 50 case MotionEvent.ACTION_UP: 51 Log.e(TAG,"onTouchEvent ACTION_UP"); 52 break; 53 case MotionEvent.ACTION_CANCEL: 54 Log.e(TAG,"onTouchEvent ACTION_CANCEL"); 55 break; 56 default: 57 break; 58 } 59 return super.onTouchEvent(event); 60 } 61 }
1 <com.example.zy.internetandlife.ViewTestDemo.MyTextView 2 android:layout_width="wrap_content" 3 android:layout_height="18dp" 4 android:text="MyTextView" 5 android:id="@+id/my_text_view" 6 app:layout_constraintBottom_toBottomOf="parent" 7 app:layout_constraintLeft_toLeftOf="parent" 8 app:layout_constraintRight_toRightOf="parent" 9 app:layout_constraintTop_toTopOf="parent" />
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener { 2 private static final String TAG="MainActivity"; 3 private MyTextView myTextView; 4 @Override 5 protected void onCreate(Bundle savedInstanceState) { 6 super.onCreate(savedInstanceState); 7 setContentView(R.layout.activity_main); 8 myTextView=(MyTextView)findViewById(R.id.my_text_view); 9 myTextView.setOnClickListener(this); 10 myTextView.setOnTouchListener(this); 11 } 12 @Override 13 public boolean dispatchTouchEvent(MotionEvent ev){ 14 switch (ev.getAction()){ 15 case MotionEvent.ACTION_DOWN: 16 Log.e(TAG,"dispatchTouchEvent ACTION_DOWN"); 17 break; 18 case MotionEvent.ACTION_MOVE: 19 Log.e(TAG,"dispatchTouchEvent ACTION_MOVE"); 20 break; 21 case MotionEvent.ACTION_UP: 22 Log.e(TAG,"dispatchTouchEvent ACTION_UP"); 23 break; 24 case MotionEvent.ACTION_CANCEL: 25 Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL"); 26 break; 27 default: 28 break; 29 } 30 return super.dispatchTouchEvent(ev); 31 } 32 @Override 33 public boolean onTouchEvent(MotionEvent event){ 34 switch(event.getAction()){ 35 case MotionEvent.ACTION_DOWN: 36 Log.e(TAG,"onTouchEvent ACTION_DOWN"); 37 break; 38 case MotionEvent.ACTION_MOVE: 39 Log.e(TAG,"onTouchEvent ACTION_MOVE"); 40 break; 41 case MotionEvent.ACTION_UP: 42 Log.e(TAG,"onTouchEvent ACTION_UP"); 43 break; 44 case MotionEvent.ACTION_CANCEL: 45 Log.e(TAG,"onTouchEvent ACTION_CANCEL"); 46 break; 47 default: 48 break; 49 } 50 return super.onTouchEvent(event); 51 } 52 @Override 53 public void onClick(View v) { 54 switch(v.getId()){ 55 case R.id.my_text_view: 56 Log.e(TAG, "MyTextView onClick" ); 57 break; 58 default: 59 break; 60 } 61 } 62 63 @Override 64 public boolean onTouch(View v, MotionEvent event) { 65 switch (v.getId()){ 66 case R.id.my_text_view: 67 switch (event.getAction()){ 68 case MotionEvent.ACTION_DOWN: 69 Log.e(TAG,"MyTextView onTouch ACTION_DOWN"); 70 break; 71 case MotionEvent.ACTION_MOVE: 72 Log.e(TAG,"MyTextViEW onTouch ACTION_MOVE"); 73 break; 74 case MotionEvent.ACTION_UP: 75 Log.e(TAG,"MyTextView onTouch ACTION_UP"); 76 break; 77 default: 78 break; 79 } 80 break; 81 default: 82 break; 83 } 84 return false; 85 } 86 }
在此,存在三种情况:
1)返回false;
2)返回true;
3)父类的同名方法
结论:
1)触摸事件的传递流程是从dispatchTouchEvent开始的,如果不进行人为干预,则会以找嵌套层次由外向内传递,到达最内层的View时,由onTouchEvent方法处理,如果该方法能够消费该事件,则返回true,否则false.这时事件会重新向外层传递,并由外层View的onTouchEvent方法进行处理.
2)如果进行人为干预,事件处理函数返回true,则会导致事件题前被消费掉,内层View将不会收到这个事件。
3)View触发事件顺序是先执行onTouch,在最后才执行onClick。如果onTouch返回true,则事件不会继续传递,最后也不会调用onClick方法;如果onTouch返回false,则事件继续传递。
4.ViewGroup的事件传递机制
ViewGroup和View的唯一区别是多了一个onInterceptTouchEvent方法。
1 <?xml version="1.0" encoding="utf-8"?> 2 <com.example.zy.internetandlife.ViewTestDemo.MyRelativeLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 xmlns:tools="http://schemas.android.com/tools" 7 tools:context="com.example.zy.internetandlife.MainActivity"> 8 9 10 <com.example.zy.internetandlife.ViewTestDemo.MyTextView 11 android:id="@+id/my_text_view" 12 android:textSize="20sp" 13 android:text="Hello ViewSet!" 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" /> 16 </com.example.zy.internetandlife.ViewTestDemo.MyRelativeLayout>
1 public class MyRelativeLayout extends RelativeLayout { 2 3 private static final String TAG="MyRelativeLayout"; 4 5 public MyRelativeLayout(Context context) { 6 super(context); 7 } 8 9 public MyRelativeLayout(Context context, AttributeSet attrs) { 10 super(context, attrs); 11 } 12 13 public boolean dispatchTouchEvent(MotionEvent ev){ 14 switch (ev.getAction()){ 15 case MotionEvent.ACTION_DOWN: 16 Log.e(TAG,"dispatchTouchEvent ACTION_DOWN"); 17 break; 18 case MotionEvent.ACTION_MOVE: 19 Log.e(TAG,"dispatchTouchEvent ACTION_MOVE"); 20 break; 21 case MotionEvent.ACTION_UP: 22 Log.e(TAG,"dispatchTouchEvent ACTION_UP"); 23 break; 24 case MotionEvent.ACTION_CANCEL: 25 Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL"); 26 break; 27 default: 28 break; 29 } 30 return super.dispatchTouchEvent(ev); 31 } 32 33 public boolean onTouchEvent(MotionEvent event){ 34 switch(event.getAction()){ 35 case MotionEvent.ACTION_DOWN: 36 Log.e(TAG,"onTouchEvent ACTION_DOWN"); 37 break; 38 case MotionEvent.ACTION_MOVE: 39 Log.e(TAG,"onTouchEvent ACTION_MOVE"); 40 break; 41 case MotionEvent.ACTION_UP: 42 Log.e(TAG,"onTouchEvent ACTION_UP"); 43 break; 44 case MotionEvent.ACTION_CANCEL: 45 Log.e(TAG,"onTouchEvent ACTION_CANCEL"); 46 break; 47 default: 48 break; 49 } 50 return super.onTouchEvent(event); 51 } 52 53 public boolean onIterceptTouchEvent(MotionEvent ev){ 54 switch(ev.getAction()){ 55 case MotionEvent.ACTION_DOWN: 56 Log.e(TAG,"OnInterceptTouchEvent ACTION_DOWN"); 57 break; 58 case MotionEvent.ACTION_MOVE: 59 Log.e(TAG,"OnInterceptTouchEvent ACTION_MOVE"); 60 break; 61 case MotionEvent.ACTION_UP: 62 Log.e(TAG,"OnInterceptTouchEvent ACTION_UP"); 63 break; 64 default: 65 break; 66 } 67 return super.onInterceptTouchEvent(ev); 68 } 69 }
不一样的地方:MainActivity和MyTextView之间增加了一层MyRelativeLayout。
结论:
1)触摸事件的传递顺序是由Activity到ViewGroup,再由ViewGroup递归传递给它的子View。
2)ViewGroup通过InterceptTouchEvent方法对事件进行拦截,如果该方法返回true,则事件不会继续传递给子View,如果返回false或者super.onInterceptTouchEvent,则事件继续传递给子View.
3)在子View中对事件进行消费后,ViewGroup将接收不到任何事件。