Android开发的过程中经常会遇到多个View,ViewGroup的嵌套问题,比如banner图里面的ViewPager嵌套Fragment,滑动的时候banner的滑动事件回合ViewPager的滑动事件相互冲突,这时候需要深入理解View的事件传递机制。
1触摸事件的类型
触摸事件是MotionEvent类,对应的事件类型有3种,ACTION_DOWN(触摸事件的开始),ACTION_MOVE(移动的时候触发),ACTION_UP(触摸事件的结束)。
2.触摸事件的三个阶段
分发(Dispatch)
无论哪种事件表现类型,首先都是基于事件的传递模型。其实Android中的事件传递有点类似于JS中事件传递模型。都是基于先捕获然后冒泡的形式。
在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程;
在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。
事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。
主要表现在可以控制每层事件是否继续传递(由事件分发和事件拦截协同进行),以及事件的具体消费(由事件消响应进行,但需要注意的是,事件分发自身也具有事件消费能力)。
也就是本文提及的事件分发、拦截和响应。
Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元不具有事件分发和事件拦截(因为它没有自己的子View)。
事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。无论是Activity还是View,如前文所说,事件分发自身也具有消费能力,
如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。如果你不想Activity中的任何控件具有任何的事件消费能力,
最简答的方法可以重写此Activity的dispatchTouchEvent方法,直接返回true就ok。
如果事件分发返回 false,表明事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。
当然了,如果本层控件已经是Activity,那么事件将被系统消费或处理。
如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理
(如果本层控件是Activity,由于其没有事件拦截,因此将直接将事件传递到子View,并交给子View的事件分发进行处理)。
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;
如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。
如果返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,交由子View的dispatchTouchEvent进行处理。
事件响应:public boolean onTouchEvent(MotionEvent ev)
如果onTouchEvent返回true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的冒泡。
如果onTouchEvent返回false,事件在onTouchEvent中处理后继续向上层View冒泡,且有上层View的onTouchEvent进行处理。
如果返回super.onTouchEvent(ev),则默认处理的逻辑和返回false时相同。
总结:从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,
只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,
但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否具有冒泡特是由onTouchEvent的返回值决定的。
Test_case
布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.ldw.event.TouchEventFather xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#468AD7"
android:gravity="center"
android:orientation="vertical" >
<com.ldw.event.TouchEventChilds
android:id="@+id/childs"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
android:background="#E1110D"
android:text="@string/hello" />
</com.ldw.event.TouchEventFather>
逻辑代码TouchEventActivity.java
package com.ldw.event;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
public class TouchEventActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.w("eventTest", "Activity | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.dispatchTouchEvent(ev);
//return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.w("eventTest", "Activity | onTouchEvent --> " + TouchEventUtil.getTouchAction(event.getAction()));
return super.onTouchEvent(event);
}
}
TouchEventChilds.java
package com.ldw.event;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class TouchEventChilds extends LinearLayout {
public TouchEventChilds(Context context) {
super(context);
}
public TouchEventChilds(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("eventTest", "Childs | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.dispatchTouchEvent(ev);
//return true;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("eventTest", "Childs | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
//return super.onInterceptTouchEvent(ev);
return false;
}
public boolean onTouchEvent(MotionEvent ev) {
Log.d("eventTest", "Childs | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
//return super.onTouchEvent(ev);
return true;
}
}
TouchEventFather.java
package com.ldw.event;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class TouchEventFather extends LinearLayout {
public TouchEventFather(Context context) {
super(context);
}
public TouchEventFather(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("eventTest", "Father | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.dispatchTouchEvent(ev);
//return true;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("eventTest", "Father | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.onInterceptTouchEvent(ev);
//return false;
}
public boolean onTouchEvent(MotionEvent ev) {
Log.d("eventTest", "Father | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.onTouchEvent(ev);
}
}
TouchEventUtil.java触摸
package com.ldw.event;
import android.view.MotionEvent;
public class TouchEventUtil {
public static String getTouchAction(int actionId) {
String actionName = "Unknow:id=" + actionId;
switch (actionId) {
case MotionEvent.ACTION_DOWN:
actionName = "ACTION_DOWN";
break;
case MotionEvent.ACTION_MOVE:
actionName = "ACTION_MOVE";
break;
case MotionEvent.ACTION_UP:
actionName = "ACTION_UP";
break;
case MotionEvent.ACTION_CANCEL:
actionName = "ACTION_CANCEL";
break;
case MotionEvent.ACTION_OUTSIDE:
actionName = "ACTION_OUTSIDE";
break;
}
return actionName;
}
}
配置文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ldw.event"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="com.ldw.event.TouchEventActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>