触摸事件相关方法:
ViewGroup
- dispatchTouchEvent(MotionEvent) 用于分发touch事件
- onInterceptTouchEvent(MotionEvent) 用于是否中断touch事件
- onTouchEvent(MotionEvent) 用于处理touch事件
View、Activity
- dispatchTouchEvent(MotionEvent)
- onTouchEvent(MotionEvent)
具体代码如下:
1、Utils.java 代码如下:
public class Utils {
/**
* 获取触摸事件的Action名称
* @param ev
*/
public static String getActionName(MotionEvent ev) {
String action;
switch (ev.getAction()) {
case MotionEvent.ACTION_CANCEL:
action = "CANCEL";
break;
case MotionEvent.ACTION_DOWN:
action = "DOWN";
break;
case MotionEvent.ACTION_MOVE:
action = "MOVE";
break;
case MotionEvent.ACTION_UP:
action = "UP";
break;
default:
action = "UNKNOWN_ACTION";
break;
}
if (action.length() < 5) {
for (int i = action.length(); i < 5; i++) {
action += " ";
}
}
return action;
}
}
2、ViewGroupA.java 代码如下:
public class ViewGroupA extends LinearLayout {
public ViewGroupA(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
System.out.println(Utils.getActionName(ev) + ", ViewGroupA.dispatch");
boolean result = super.dispatchTouchEvent(ev);
System.out.println(Utils.getActionName(ev) + ", ViewGroupA.dispatch = " + result);
return result;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = false;
System.out.println(Utils.getActionName(ev) + ", ViewGroupA.intercept = " + result);
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = false;
System.out.println(Utils.getActionName(event) + ", ViewGroupA.touch = " + result);
return result;
}
}
布局效果如下:
运行代码,在蓝色的View上进行按下、移动、抬起,输出的Log如下:
---------------------------------------------
DOWN , Activity.dispatch
DOWN , ViewGroupA.dispatchDOWN , ViewGroupA.intercept = falseDOWN , ViewGroupB.dispatchDOWN , ViewGroupB.intercept = falseDOWN , ViewC.dispatchDOWN , ViewC.touch = falseDOWN , ViewC.dispatch = falseDOWN , ViewGroupB.touch = falseDOWN , ViewGroupB.dispatch = falseDOWN , ViewGroupA.touch = falseDOWN , ViewGroupA.dispatch = falseDOWN , Activity.touch = falseDOWN , Activity.dispatch = false---------------------------------------------
MOVE , Activity.dispatchMOVE , Activity.touch = falseMOVE , Activity.dispatch = false---------------------------------------------
UP , Activity.dispatchUP , Activity.touch = falseUP , Activity.dispatch = false---------------------------------------------
从上面的Log可分析出:
- 触摸事件最先是由Activity获得,然后是ViewGroupA、ViewGroupB、ViewC
- 如果Down事件没有人处理,则事件丢失,后续的事件(如Move、Up)不再传递,直接由Activity进行处理
- 所有的事件分发都是调用super.dispatchTouchEvent(ev)完成的,所以如果不调用这句代码则事件中止传递。但是要中止事件传递一般不会这么做,一般是在onInterceptTouchEvent(MotionEvent) 方法中处理,如果该方法返回true则中止。既然dispatchTouchEvent(ev)方法可以中止事件传递,为什么还要设计一个onInterceptTouchEvent(MotionEvent) 方法呢? 因为子View可以请求父View不要拦截事件,如ListView是可以上下滑动的,当处于滑动状态时候就会请求禁止父View的拦截触摸事件方法,让ListView可以一直获取到touch事件进行滚动。假设这个时候父View又想响应触摸事件怎么办?可以写到dispatchTouchEvent方法中,因为事件是先传到这个方法,然后再传递给ListView的。
- 既然Activity最先获得事件,则可在Activity的dispatchTouchEvent方法不调用super.dispatchTouchEvent(ev);代码,则事件就不会进行分发了,可在此方法中调用onTouchEvent(ev)让它去处理。
- 应用:假如在Activity中要一定要响应一些触摸事件,又怕事件传递后被消费了,也是相同道理,直接在Activity的dispatchTouchEvent方法中处理即可)
修改ViewGroupA的onInterceptTouchEvent让其返回true,代表拦截事件。
运行,然后按下、移动、抬起,Log如下:
---------------------------------------------
DOWN , Activity.dispatchDOWN , ViewGroupA.dispatchDOWN , ViewGroupA.intercept = trueDOWN , ViewGroupA.touch = falseDOWN , ViewGroupA.dispatch = falseDOWN , Activity.touch = falseDOWN , Activity.dispatch = false---------------------------------------------
MOVE , Activity.dispatchMOVE , Activity.touch = falseMOVE , Activity.dispatch = false---------------------------------------------
UP , Activity.dispatchUP , Activity.touch = falseUP , Activity.dispatch = false
从上面的Log可分析出:
- ViewGroupA在Down事件的时候中断事件传递后直接把Touch事件交由自己的onTouchEvent方法去处理
- ViewGroupA中断触摸事件后只是子View(ViewGroupB和ViewC)不再获得touch事件,而父View(Activity)可以。
- 虽然ViewGroupA中断触摸事件传递,由于它的onTouchEvent方法也没有处理Down事件,所以事件也丢失了,后续事件(如Move、Up)不再传给ViewGroupA,由Activity中行处理。所以,如果你想要获取后续事件,在处理了Down事件后一定要记得返回true。这里说的后续事件是指一次整体的操作,一般是由一个Down和多个Move和一个Up组成。按下然后移动然后弹起,这样的操作称为一次整体的操作。
修改ViewGroupA的onInterceptTouchEvent让其返回false,代表不拦截事件
修改ViewGroupB的onInterceptTouchEvent让其返回true,代表拦截事件,并修改onTouchEvent方法如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEvent = super.onTouchEvent(event);;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onTouchEvent = true;
break;
case MotionEvent.ACTION_MOVE:
onTouchEvent = false;
break;
case MotionEvent.ACTION_UP:
onTouchEvent = false;
break;
default:
break;
}
System.out.println(MotionEventUtil.getMotionEventActionName(event) + ", ViewGroupB.touch = " + onTouchEvent);
return onTouchEvent;
}
运行,然后按下、移动、抬起,Log如下:
---------------------------------------------
DOWN , Activity.dispatch
DOWN , ViewGroupA.dispatch
DOWN , ViewGroupA.intercept = false
DOWN , ViewGroupB.dispatch
DOWN , ViewGroupB.intercept = true
DOWN , ViewGroupB.touch = true
DOWN , ViewGroupB.dispatch = true
DOWN , ViewGroupA.dispatch = true
DOWN , Activity.dispatch = true
---------------------------------------------
MOVE , Activity.dispatch
MOVE , ViewGroupA.dispatch
MOVE , ViewGroupA.intercept = false
MOVE , ViewGroupB.dispatch
MOVE , ViewGroupB.touch = false
MOVE , ViewGroupB.dispatch = false
MOVE , ViewGroupA.dispatch = false
MOVE , Activity.touch = false
MOVE , Activity.dispatch = false
---------------------------------------------
MOVE , Activity.dispatch
MOVE , ViewGroupA.dispatch
MOVE , ViewGroupA.intercept = false
MOVE , ViewGroupB.dispatch
MOVE , ViewGroupB.touch = false
MOVE , ViewGroupB.dispatch = false
MOVE , ViewGroupA.dispatch = false
MOVE , Activity.touch = false
MOVE , Activity.dispatch = false
---------------------------------------------
UP , Activity.dispatch
UP , ViewGroupA.dispatch
UP , ViewGroupA.intercept = false
UP , ViewGroupB.dispatch
UP , ViewGroupB.touch = false
UP , ViewGroupB.dispatch = false
UP , ViewGroupA.dispatch = false
UP , Activity.touch = false
UP , Activity.dispatch = false
从上面的Log可分析出:
- ViewGroupB中断了触摸事件的传递,并把事件交给自己的onTouchEvent方法处理。它的孩子ViewC就接收不到touch事件了
- ViewGroupB中消费了Down事件(返回true),所以可以接收后续的Move、Up事件,dispatch方法接收到Move事件后并没有去调用onInterceptTouchEvent方法了,因为在接收到Down事件时候已经成为了目标View,非目标View不能接收后续事件,所以后续事件(Move、Up)就不需要再调用了onInterceptTouchEvent,因为不需要再传递了,所以直接把事件交给自己的onTouchEvent方法进行处理。
- 虽然ViewGroupB在处理Move事件时返回了false,但是父View(ViewGroupA)的onTouchEvent方法并没有执行,这说明,谁消费了Down事件(返回true)谁就能接收后续事件(Move、Up),没有消费Down事件的其它View则接收不到(Activity例外)。
- 虽然ViewGroupB在处理Move事件时返回了false,但是还是依旧可以接收后续事件的(Move、Up),那在处理Move和UP时返回false或返回true有什么区别吗?答:返回true则消费此事件,Activity的onTouchEvent方法就不会接收到了。
通过这个Demo可以模拟任何的不同情况,如处理Down事件返回true,Move事件返回false,事件拦截,请求禁止拦截等等。
总结:
- touch事件传递其实就是一连串的方法调用,由Activity的dispatchTouchEvent方法开始调用,当这个方法调用结束时,这个touch事件就结束了。
- 一个整体的touch事件由1个Donw和0 ~ n个Move和1个Up事件组成
- 消费了Down事件的View称为目标View,目标View可接收后续事件(非目标View不接收后续事件)
- Down事件如果没有任何View消费,则后续事件不再传递,直接由Activity的onTouchEvent方法处理,所以,想要处理其它事件首先要消费Down事件,也就是在接收到Down事件的时候返回true。
- 拦截:
- 如果在Down事件拦截,则把当前事件交自己的onTouchEvnet方法处理,如果此方法不消费Down事件,则事件丢失,后续事件由Activity的onTouchEvent方法处理
- 如果在其它事件拦截,则不处理当前事件,且传一个Cancel事件给目标View,后续的事件就会交给自己的onTouchEvent方法处理(此时拦截了事件的View变成了目标View,虽然它没有消费Down事件)
- 如果在Down事件不拦截,在事件分发返回后,如果Down事件没被消费,则会把事件交给自己的onTouchEvent方法处理。
- 如果在其它事件不拦截(能接收其它事件说明有目标View),在事件分发返回后,不会把事件交给自己的onTouchEvent,即使目标View在处理(Move、Up)事件时返回false。
- Activity不管有无目标View,也不管是Down还是Move、Up事件,只要分发调用返回时,如果事件没被消费,则交给自己的onTouchEvent方法处理
- 可以调用getParent().requestDisallowInterceptTouchEvent(true)方法请求父View禁止拦截事件,这个方法会递归的请求所有的父View禁止拦截事件。
- 注:如果想要获取到一个整体的touch事件,一定要消费Down事件,如果在Down事件的时候只是请求父View禁止拦截并不消费Down事件,虽然父View不再拦截了,但后续事件也接收不到了,哪个父View消费了Down事件哪个父View就可以接收到后续事件。
- 有两个View并没有包含关系,但是有重叠,则上面的View先拿到事件,如果消费了,则事件不会传给另一个View。
- 容器类一般都是调用ViewGroup的dispatchTouchEvent方法进行事件分发,其它类一般是调用View类的dispatchTouchEvent方法进行事件分发;通常默认的onInterceptTouchEvent、onTouchEvent方法都是返回false。
- 应用技巧:
- 父类的onTouchEvent方法要想执行,要么是等所有的子View都不消费Down事件,要么是父View把事件拦截。
- 如果子类消费了Down事件,而父View又不想拦截但是又想处理这个事件,则父View可以在onInterceptTouchEvent或dispatchTouchEvent方法处理touch事件
- 如果子View请求了禁止父View拦截,且父View还想要拦截的话,可在父View的dispatchTouchEvent方法中不调用super.dispatchTouchEvent则把事件拦截了。
作者:luomoBM 发表于2016/10/8 9:42:52 原文链接
阅读:16 评论:0 查看评论