Touch事件传递和拦截机制
关于touch事件的主要方法介绍
1. ViewGroup的子类(容器类,如:LinearLayout,RelativeLayout等)
1. dispatchTouchEvent(MotionEvent ev) 事件的分发
2. onInterceptTouchEvent(MotionEvent ev) 事件的拦截
3. onTouchEvent(MotionEvent ev) 事件的处理
2. View的子类(非容器类,如:TextView,ImageView等)
1. dispatchTouchEvent(MotionEvent ev) 事件的分发
2. onTouchEvent(MotionEvent ev) 事件的处理
区别:
非容器类没有onInterceptTouchEvent(MotionEvent ev) 事件的拦截
下面是测试案例的主要代码
1. 打印日志的类
public class LogUtils {
public static void printLog(String tag,MotionEvent ev,String touchName) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下
Log.i(tag, touchName + "按下");
break;
case MotionEvent.ACTION_MOVE:
//按下
Log.d(tag, touchName + "移动");
break;
case MotionEvent.ACTION_UP:
//按下
Log.e(tag, touchName + "松开");
break;
default:
break;
}
}
}
2. 第一个容器类(为了方便布局,本案例中用FrameLayout做基类,在事件拦截机制中和直接继承ViewGroup类一致,因为FrameLayout没有覆盖ViewGroup的上面三个touch方法)
public class ViewGroupOne extends FrameLayout {
public ViewGroupOne(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public ViewGroupOne(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupOne",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupOne",ev, "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
//处理事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupOne",ev, "onTouchEvent");
return super.onTouchEvent(ev);
}
}
3. 第二个容器类
public class ViewGroupTwo extends FrameLayout {
public ViewGroupTwo(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public ViewGroupTwo(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
//处理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return super.onTouchEvent(event);
}
}
4. 非容器类
public class MyView extends View {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
// 分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
// 处理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",event, "onTouchEvent");
return super.onTouchEvent(event);
}
}
5. 主界面的布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<com.itheima13.eventdemo.ViewGroupOne
android:layout_width="200dip"
android:layout_height="200dip"
android:background="#ff0000" >
<com.itheima13.eventdemo.ViewGroupTwo
android:layout_width="150dip"
android:layout_height="150dip"
android:background="#00ff00" >
<com.itheima13.eventdemo.MyView
android:layout_width="100dip"
android:layout_height="100dip"
android:background="#0000ff" />
</com.itheima13.eventdemo.ViewGroupTwo>
</com.itheima13.eventdemo.ViewGroupOne>
</RelativeLayout>
预览结果如下图(红色是ViewGroupOne,绿色是ViewGroupTwo,蓝色是MyView):
上面原始代码都是默认的情况,运行后点击蓝色区域(按下,移动,松开)结果如图:
总结: 默认情况只相应touch事件的down状态,而且事件默认传递到最内层的子控件(MyView)onTouchEvent方法,由于默认MyView的onTouchEvent的方法没有处理,所以事件依次回传至父控件(ViewGroupTwo,ViewGroupOne)的onTouchEvent方法,由于每层都没处理,所以事件回传至最外层父控件后消失
由于情况复杂,下面以三个方法为核心介绍所有情况。(一个案例测试一种情况,每个案例的代码改变都是在上面的基础代码上修改测试,也就是每个案例每种情况的代码都是独立的,只需参考上面的基本代码和案例中每种情况修改的代码即可。)
dispatchTouchEvent(MotionEvent ev)方法
* 有三种情况
* 直接返回true
* 只修改ViewGroupTwo的dispatchTouchEvent方法测试
//分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return true;
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
* 只修改MyView的dispatchTouchEvent方法测试
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
return true;//super.dispatchTouchEvent(ev);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
结果分析:
事件传递至子控件的dispatchTouchEvent终止。事件只有按顺序传递,没有回传情况。并且事件的三种情况(down,move,up)都有处理回调
* 直接返回false
* 只修改ViewGroupTwo的dispatchTouchEvent方法测试
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return false;//super.dispatchTouchEvent(ev);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
* 只修改MyView的dispatchTouchEvent方法测试
// 分发事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
return false;//super.dispatchTouchEvent(ev);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
结果分析:
事件传递至当前控件的dispatchTouchEvent终止,并且事件回传给父控件的onTouchEvent方法,此过程中只相应事件的down状态
* 直接调用super.dispatchTouchEvent(ev);
结果分析:默认情况,直接调用ViewGroup的dispatchTouchEvent(ev)方法,事件会传递给当前的控件的onInterceptTouchEvent(MotionEvent ev)方法。
onInterceptTouchEvent(MotionEvent ev) 方法
* 有三种情况
* 直接返回true
* 只修改ViewGroupTwo的onInterceptTouchEvent方法
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
return true;//super.onInterceptTouchEvent(ev);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
结果分析:touch事件被当前控件拦截,不传递给子控件,并调用自己的onTouchEvent方法处理事件
* 直接返回false
* 只修改ViewGroupTwo的onInterceptTouchEvent方法
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
return false;//super.onInterceptTouchEvent(ev);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
结果分析:touch事件传递给子控件,由子控件处理事件
* 直接调用super.onInterceptTouchEvent(ev) (调用ViewGroup的方法)
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
结果分析:touch事件传递给子控件,由子控件处理事件(与直接返回false类似)
onTouchEvent(MotionEvent ev) 方法
* 有三种情况
* 直接返回true
* 只修改ViewGroupTwo的onTouchEvent方法
//处理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return true;//super.onTouchEvent(event);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
结果分析:down状态:当touch事件回传给当前控件的时候,由于返回ture,所有事件回传终止。针对move和up状态:事件到当前的onTouchEvent方法终止。也就是说子控件无法响应事件。
* 直接返回false
* 只修改ViewGroupTwo的onTouchEvent方法
//处理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return false;//super.onTouchEvent(event);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
>结果分析:事件回传给父控件
* 直接调用super.onTouchEvent(ev)
* 只修改ViewGroupTwo的onTouchEvent方法
//处理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return super.onTouchEvent(event);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
>结果分析:事件回传给父控件
事件反向拦截
主要是反向拦截父控件的onInterceptTouchEvent
* 没申请反向拦截父控件onInterceptTouchEvent的方法
1. 只在ViewGroupTwo中覆盖了onTouchEvent方法,并且事件消费
//处理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return true;//super.onTouchEvent(event);
}
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
* 申请反向拦截父控件的onInterceptTouchEvent的方法
1. 在上面的代码基础上,添加dispatchTouchEvent方法的覆盖
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
requestDisallowInterceptTouchEvent(true);
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
注意多了requestDisallowInterceptTouchEvent(true);方法调用,该方法是拦截父控件的onInterceptTouchEvent方法的执行(针对move,up状态)
运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
>结果分析: 申请反向拦截后,父控件的onInterceptTouchEvent方法将不再执行,案例如:智慧北京项目的轮播图事件反拦截
>源码解释:
requestDisallowInterceptTouchEvent(true); 方法源码如:
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
//主要是记录拦截的标记 看dispatchTouchEvent方法的代码片段
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
//继续设置父控件的拦截模式
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
dispatchTouchEvent方法代码片段:
对上面设置的状态值mGroupFlags判断
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {//根据状态值是否调用onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
要点解析:
看运行效果图可知,down事件执行的时候,已经设置了反向拦截的状态,所有到move,up状态执行的时候,判断就起到了拦截的效果
#### 事件传递和拦截流程图(针对down事件的流程)
实际操作会以下2点就可以了
1. 只需要做自己onTouchEvent方法的事件处理,并且返回值设置为true就可以满足自己事件处理
2. 申请不让父控件的onInterceptTouchEvent的方法执行
本文版权归传智播客Android培训学院所有,欢迎转载,转载请注明作者出处。谢谢!
作者:传智播客Android培训学院
首发:http://www.itcast.cn/android/