要想很好的自定义view与viewgroup,那么对于事件分发机制,需要了解的非常透彻才能使用的游刃有余。
这里参考了郭大神的文章,并且对其中讲解的不太理解的地方,做了深入的探究和详细的解析。
这里先做简要的介绍:
一个动作,点击动作(按下+抬起),滑动动作(按下+滑动+抬起)
这样的一个动作,下文称为”大事件”。
一个动作(事件),是由很多的”小事件”序列组成的。
主要的小动作(小事件)就只有三种:down,move,up
一: view的事件分发
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false; //注意这里是false
}
});
setOnClickListener,setOnTouchListener两个方法都可以实现点击事件,而onTouchListener事件可以做更多的事,因为他有个参数MotionEvent 可以判断手指按下,抬起,移动。
下面仅仅是一个简单的点击,输出:
onTouch execute, action 0// 点下事件
onTouch execute, action 2// 移动事件
onTouch execute, action 1//抬起事件
onClick execute//最后触发点击事件,一个点击事件就是(按下+抬起)
下面是点击+移动+抬起事件,输出:
onTouch execute, action 0// 点下事件
onTouch execute, action 2// 移动事件
onTouch execute, action 2// 移动事件
onTouch execute, action 2// 移动事件
onTouch execute, action 1//抬起事件
onClick execute//最后触发点击事件,一个点击事件就是(按下+抬起)
总结一下:在一次事件传递中onTouch()是先执行,而onclick()是后执行的。
下面把onTouch事件返回值改成true:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return true; //注意这里是true
}
});
输出:
onTouch execute, action 0
onTouch execute, action 2
onTouch execute, action 1
或者
onTouch execute, action 0
onTouch execute, action 2
onTouch execute, action 2
onTouch execute, action 1
onClick方法没有没执行。
总结:onTouch方法返回true,代表这个action小事件被onTouch消费掉了,因而不会再继续向下执行。
普及一点知识:
任何一个view控件被触摸到,都会调用dispatchTouchEvent方法(dispatchTouchEvent是View的一个方法)。
//这里是dispatchTouchEvent部分代码
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
1.mOnTouchListener != null//注册了onTouchListener事件
2.(mViewFlags & ENABLED_MASK) == ENABLED//当前点击的控件是否是enable的,所以的view默认都是enabled的,恒true
3.mOnTouchListener.onTouch(this, event)//onTouch返回true
当1,2,3条件都满足的时候,直接就返回true,事件就结束了,不会继续往下执行。(满足上面的实验,onTouch返回true,onClick就没执行)
那么,当onTouch返回false的时候,就会执行onTouchEvent(event)。
由此,onClick方法可想而知,就在onTouchEvent里面执行的。
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //如果view是可点击的
switch (event.getAction()) {
case MotionEvent.ACTION_UP: //此刻的事件为抬起手指
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick(); //这里面执行的就是onClick方法(如果设置了onClick)
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true; //不管前面的actin是什么事件,都会执行到这里,返回true。
}
return false;
}
1.view 可点击
2.事件是手指抬起
3.设置了onClickListener监听
满足三个条件,才会执行到onClick方法。
注意:如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action都不会传递到该dispatchTouchEvent。
明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。
(相信“注意”这一段话大家还是不太好理解,这里来做非常详细的实验)
自定义一个button,重写dispatchTouchEvent(MotionEvent event)方法,输出调用信息。
动作:点击一个VIEW,输出:
dispatchTouchEvent event=MotionEvent { action=ACTION_DOWN}
onTouch execute, action 0//小事件一,按下
dispatchTouchEvent event=MotionEvent { action=ACTION_MOVE}
onTouch execute, action 2//小事件二,移动
dispatchTouchEvent event=MotionEvent { action=ACTION_UP }
onTouch execute, action 1//小事件三,抬起
分析:
宏观的一个点击的”大事件”,实际这里发生了三个小事件,每个小事件都是从dispatchTouchEvent开始的。
动作:在View上滑动,输出:
dispatchTouchEvent event=MotionEvent { action=ACTION_DOWN}
onTouch execute, action 0//小事件一,按下
dispatchTouchEvent event=MotionEvent { action=ACTION_MOVE}
onTouch execute, action 2//小事件二,移动
dispatchTouchEvent event=MotionEvent { action=ACTION_MOVE}
onTouch execute, action 2//小事件三,移动
dispatchTouchEvent event=MotionEvent { action=ACTION_UP}
onTouch execute, action 1//小事件四,抬起
分析:
宏观的一个滑动的”大事件”,这里发生了四个以上的小事件,每个小事件都是从dispatchTouchEvent开始的。
这些看似分离的事件,实际上又是相互关联的。只有前一个小事件action返回true,后一个action小事件才会执行。
这里做一个实验,当小事件分发到第5个,dispatchTouchEvent返回false,看看后面的小事件还能不能继续执行。
int count = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
count ++;
Log.d("TAG","dispatchTouchEvent count = "+count+" event="+event );
if (count == 5) return false;//小事件执行到第5次就返回false
boolean result = super.dispatchTouchEvent(event);
return result;
}
动作:在View上面滑动 ,输出:
dispatchTouchEvent count = 1 event=MotionEvent { action=ACTION_DOWN}//返回true,第1个小事件正常(按下)
onTouch execute, action 0
dispatchTouchEvent count = 2 event=MotionEvent { action=ACTION_MOVE}//返回true,第2个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 3 event=MotionEvent { action=ACTION_MOVE}//返回true,第3个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 4 event=MotionEvent { action=ACTION_MOVE}//返回true,第4个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 5 event=MotionEvent { action=ACTION_MOVE}//返回false,第5个小事件不执行(滑动)
dispatchTouchEvent count = 6 event=MotionEvent { action=ACTION_MOVE}//返回true,第6个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 7 event=MotionEvent { action=ACTION_MOVE}//返回true,第7个小事件正常(滑动)
onTouch execute, action 2
dispatchTouchEvent count = 8 event=MotionEvent { action=ACTION_UP}//返回true,第8个小事件正常(抬起)
onTouch execute, action 1
可以看到,中途有一个count==5的时候,这个小事件返回了false。而接下来的小事件action,还是可以继续传递到dispatchTouchEvent 的。
第二种情况:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
count ++;
Log.d("TAG","dispatchTouchEvent count = "+count+" event="+event );
if (count == 1) return false;//小事件执行了1次就返回false
boolean result = super.dispatchTouchEvent(event);
return true;
}
动作:任何动作, 输出:
dispatchTouchEvent count = 1 event=MotionEvent { action=ACTION_DOWN}
可以看到,第一个事件就返回了false ,而后续的所有的小事件action都不再传递到dispatchTouchEvent 了。
1.一开始就返回false的,后续action不在传递,也不会传递到dispatchTouchEvent 。
2.中途返回false的,后续action还会继续传递,dispatchTouchEvent 会接收到后续action小事件。
总结:一个大动作(大事件),由很多的小事件组成,这些小事件都是从最里面的层的传递过来,由于这里是view,也就是最外层(浅层)郭神的文章里“只有前一个小事件action返回true,后一个action小事件才会执行。”在我看来,这一句话不是完全准确的。
不相信,那再做测试:
重写Button
第一种情况:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
count ++;
Log.d("TAG","dispatchTouchEvent count = "+count+" event="+event );
//if (count == 1) return false;//小事件执行了1次就返回false
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//return super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_DOWN" );
return false;//这里返回false
case MotionEvent.ACTION_MOVE:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_MOVE" );
return true;
case MotionEvent.ACTION_UP:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_UP" );
return true;
}
return true;
}
输出:
dispatchTouchEvent count = 5 event=MotionEvent
onTouch execute, action 0
onTouchEvent MotionEvent.ACTION_DOWN
第二种情况:
@Override
public boolean onTouchEvent(MotionEvent event) {
//return super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_DOWN" );
return true;
case MotionEvent.ACTION_MOVE:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_MOVE" );
return false;//这里为false
case MotionEvent.ACTION_UP:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_UP" );
return true;
}
return true;
}
输出:
dispatchTouchEvent count = 83 event=MotionEvent { action=ACTION_DOWN
onTouch execute, action 0
onTouchEvent MotionEvent.ACTION_DOWN
dispatchTouchEvent count = 84 event=MotionEvent { action=ACTION_MOVE
onTouch execute, action 2
onTouchEvent MotionEvent.ACTION_MOVE
dispatchTouchEvent count = 85 event=MotionEvent { action=ACTION_MOVE
dispatchTouchEvent count = 86 event=MotionEvent
onTouch execute, action 1
onTouchEvent MotionEvent.ACTION_UP
第三种情况:
@Override
public boolean onTouchEvent(MotionEvent event) {
//return super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_DOWN" );
return true;
case MotionEvent.ACTION_MOVE:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_MOVE" );
return true;
case MotionEvent.ACTION_UP:
Log.d("TAG","onTouchEvent "+ "MotionEvent.ACTION_UP" );
return false;//这里为false
}
return true;
}
输出:
dispatchTouchEvent count = 83 event=MotionEvent { action=ACTION_DOWN
onTouch execute, action 0
onTouchEvent MotionEvent.ACTION_DOWN
dispatchTouchEvent count = 84 event=MotionEvent { action=ACTION_MOVE
onTouch execute, action 2
onTouchEvent MotionEvent.ACTION_MOVE
dispatchTouchEvent count = 85 event=MotionEvent { action=ACTION_MOVE
dispatchTouchEvent count = 86 event=MotionEvent
onTouch execute, action 1
onTouchEvent MotionEvent.ACTION_UP
“只有前一个小事件action返回true,后一个action小事件才会执行。”这句话应该是:只有down小事件返回false,后续的小事件序列都不会再传递到这里。(最真切的说,应该是第一次进来的action小事件返回fasle,就会终止,只不过第一次进来的事件,肯定是down事件,说以可以直接说是 down事件)
有人是这样说的“如果是DOWN事件返回true,View所在的容器会记录当前DOWN事件被那个View消费了,下次容器派发MOVE事件直接调用这个View的dispatchTouchEvent,反之,如果当前view返回的是false,容器就会将事件派发给其他的View,下次就不会将MOVE事件派发给那个返回了false的view”
这里仅仅对imageview之onTouchListener
findViewById(R.id.btn).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;//
}
});
动作:点击或滑动,输出:
dispatchTouchEvent count = 2 event=MotionEvent { action=ACTION_DOWN}
onTouch execute, action 0
原因是:ImageView默认情况下是不可点击的。在onTouchEvent中,直接返回了false,使得下一个action也不能正常传递执行。当我们在为他设置onClickstener的时候,就变成可点击的了。
郭大神的事件分发机制博客里面有这样的总结,这里添加更详细的补充:
1.为什么图片轮播器里的图片使用Button而不用ImageView?
当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。
第一,在ImageView的onTouch方法里返回true,也就是dispatchEvent方法返回了true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。
第二,在布局文件里面给ImageView增加一个android:clickable=”true”的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,在onTouchEvent里面还是会返回true,ACTION_DOWN之后的其它action也是可以得到执行的。
2 .onTouch和onTouchEvent有什么区别,又该如何使用?
这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
(onTouch起到是否事件分发的决定性作用,因为设置了onTouchListener,那么条件1就true,条件2任何view都是enabled的,恒true,条件三就是onTouch的返回值)