当你点击一个view的时候,它的底层还有其他的View/ViewGroup,那么这个点击事件谁处理,它又是怎么传递的在控件树上?
我们知道点击事件是从Activity->PhoneWindow->View/ViewGroup
点击事件有三个非常重要的方法:dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent(View没有onInterceptTouchEvent,ViewGroup才有)
dispatchTouchEvent:用于进行事件的分发,
onInterceptTouchEvent:用于拦截事件,可以拦截可以不拦截,在dispatchTouchEvent中调用
onTouchEvent:处理各种的点击事件,在dispatchTouchEvent 中调用
这三个函数的关系:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean comsume = false;
if (onInterceptTouchEvent(ev)) {
comsume = onTouchEvent(ev);
}else {
comsume = child.dispatchTouchEvent(ev);
}
reture comsume;}
对于一个ViewGroup来说,点击事件产生之后会先传给它(不管activity window先),然后ViewGroup的dispatchTouchEvent 会被调用,如果这ViewGroup拦截这个事件,这个ViewGroup的onTouchEvent会被调用,如果不拦截就会传递给它的子view,直到最后,假如最后是view的话,因为没有onInterceptTouchEvent所以会直接调用onTouchEvent,假如这个view还是没有消费这个事件,就会一层层往上调用onTouchEvent的方法,最后如果还是没有消费的话会给回activity处理
小例子:
public class ViewGroupA extends LinearLayout{
private static final String TAG = "ViewGroupA";
public ViewGroupA(Context context) {
super(context);
}
public ViewGroupA(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "ViewGroupA-----onInterceptTouchEvent: ");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "ViewGroupA -----onTouchEvent: ");
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "ViewGroupA-------dispatchTouchEvent: ");
return super.dispatchTouchEvent(ev);
}
}
ViewGroupB也是如此...不贴了
public class ViewC extends View{
private static final String TAG = "ViewGroupA";
public ViewC(Context context) {
super(context);
}
public ViewC(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewC(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "ViewC onTouchEvent: ");
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "ViewC-----dispatchTouchEvent: ");
return super.dispatchTouchEvent(ev);
}
}
<?xml version="1.0" encoding="utf-8"?>
<com.example.jinxiong.viewaction.ViewGroupA
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:orientation="vertical"
android:background="@android:color/white"
tools:context="com.example.jinxiong.viewaction.MainActivity">
<com.example.jinxiong.viewaction.ViewGroupB
android:layout_width="match_parent"
android:layout_height="500dp"
android:background="@android:color/holo_red_dark"
>
<com.example.jinxiong.viewaction.ViewC
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@android:color/black"
>
</com.example.jinxiong.viewaction.ViewC>
</com.example.jinxiong.viewaction.ViewGroupB>
</com.example.jinxiong.viewaction.ViewGroupA>
点击ViewC打印的Log
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewC-----dispatchTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewC onTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
09-04 02:10:21.996 1661-1661/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA -----onTouchEvent:
因为默认情况下。ViewGroup是不会拦截的,view在clickable 和 LongClickable都为false的情况下是不会消费事件的,这里的就是不会消费的,所以onTouch方法都得到调用
这可以类比生活的事情,假设A是经理,B是组长,C是普通的职员,老总下发一个任务,经理觉得自己是管理者,这件事应该给机会下属好好表现(onInterceptTouchEvent不拦截)所以就交给了组长B了,组长手上也有几个人在管理着,觉得这事交个小的去办就好了(onInterceptTouchEvent不拦截),所以这个任务就落到了C的身上,然后C发现自己做不了,太难了。就交回给组长B来处理,组长B也发现这件事太难了,完成不了,所以就交回个经理A
任务分发的时候是大佬到小弟,做事的时候就是小弟到大佬,大佬只会做一些小弟做不了的事情
假如有一天经理大发慈悲,觉得这事还是亲历亲为好,就把任务拦截下来,那么下面的组长和普通职员就不会知道这件事,也没法去做件事了
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "ViewGroupA-----onInterceptTouchEvent: ");
return true;
}
09-04 03:40:24.996 16657-16657/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-04 03:40:24.996 16657-16657/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-04 03:40:24.996 16657-16657/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA -----onTouchEvent:
某一天组长或许也会这样.....
当有一天普通职员能完成任务了,不需要组长,经理动手了
09-04 03:43:47.434 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-04 03:43:47.434 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-04 03:43:47.434 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-04 03:43:47.434 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-04 03:43:47.434 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewC-----dispatchTouchEvent:
09-04 03:43:47.434 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewC onTouchEvent:
09-04 03:43:47.522 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-04 03:43:47.522 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-04 03:43:47.522 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-04 03:43:47.522 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-04 03:43:47.522 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewC-----dispatchTouchEvent:
09-04 03:43:47.522 20158-20158/com.example.jinxiong.viewaction D/ViewGroupA: ViewC onTouchEvent:
一个是down事件,一个是up事件这里
onTouchEvent ,setOnTouchListener ,setOnClickListener介绍
默认情况下,View不消费事件(clickable和longclickable都是false),onTouchEvent 返回的是false,当我在这个三个地方志设置了相关事件处理的逻辑,那么View会执行哪一个尼,这里就涉及到这三个优先级的问题。
举个例子说明:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewC viewC = (ViewC) this.findViewById(R.id.viewc);
// Log.d(TAG, "isClickable: " + viewC.isClickable());
// Log.d(TAG, "isLongClickable: " + viewC.isLongClickable());
// viewC.setLongClickable(true);
// viewC.setClickable(true);
viewC.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "ViewC---- OnTouchListener");
return false ;
}
});
viewC.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "ViewC---- OnClickListener");
}
});
}
}
ViewC
@Override
public boolean onTouchEvent(MotionEvent event) {
// Log.d(TAG, "ViewC onTouchEvent: ");
boolean consume = super.onTouchEvent(event);
Log.d(TAG, "ViewC onTouchEvent: " + consume);
return consume;
}
点击ViewC log(down 和 up 事件 ,click事件是down和up都出现才为一个click事件)
09-04 04:27:19.169 27472-27472/com.example.jinxiong.viewaction D/ViewGroupA: ViewC---- OnTouchListener
09-04 04:27:19.169 27472-27472/com.example.jinxiong.viewaction D/ViewGroupA: ViewC onTouchEvent: true
09-04 04:27:19.269 27472-27472/com.example.jinxiong.viewaction D/ViewGroupA: ViewC---- OnTouchListener
09-04 04:27:19.269 27472-27472/com.example.jinxiong.viewaction D/ViewGroupA: ViewC onTouchEvent: true
09-04 04:27:19.269 27472-27472/com.example.jinxiong.viewaction D/ViewGroupA: ViewC---- OnClickListener
可以看到OntouchListener是先被执行的,这里返回的OntouchListener 返回的是false,如果返回的是true,那么只会打印两个OntouchListener
然后才会去执行onTouchEvent ,可以看到还打印出来true ,这里的值是因为我们setOnClickListener设置了,而这之后没有执行setOnClickListener是因为只有一个down事件,
当up事件发生之后就会再一次执行,并且setOnClickListener得到执行,(其实用onTouchEvent 来监听click事件的话,最好还是要实现另外的一个方法performClick())
事件序列
同一个事件序列是指从手机接触屏幕到离开屏幕的那一个过程所产生的一系列事件,就是说,这个事件序列从down事件开始,经过0到多个move事件,到up事件结束。
以ViewGroup作为操作
①拦截全部,全部消耗
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "ViewGroupB------onTouchEvent: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_DOWN");
return true;
}
case MotionEvent.ACTION_MOVE: {
Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_MOVE");
return true;
}
case MotionEvent.ACTION_UP: {
Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_UP");
return true;
}
}
return true;
}
Log:
09-05 06:41:31.387 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-05 06:41:31.387 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-05 06:41:31.387 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-05 06:41:31.387 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-05 06:41:31.387 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
09-05 06:41:31.387 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------: 我消耗了ACTION_DOWN
09-05 06:41:31.471 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-05 06:41:31.471 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-05 06:41:31.471 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-05 06:41:31.471 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
09-05 06:41:31.471 14857-14857/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------: 我消耗了ACTION_UP
结论:ViewC的方法得不到调用,down事件中onInterceptTouchEvent事件得到调用,而跟他同一个事件序列的up事件就不会再次调用onInterceptTouchEvent,
boss有 A ,B 两个相关联的任务,把他分两次给经理,当A完成之后才给B,经理A把两个相关联的任务分给组长B,组长B觉得自己可以完成,就拦截了其中的一个并且做完,剩下的一个不用问拦截不拦截,都必须要组长B完成,因为这两个任务是相关的(处于同一个的事件序列),这里就没有C职员的事情了
②拦截全部,消耗一部分
①消耗down,其余不消耗
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "ViewGroupB------onTouchEvent: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_DOWN");
return true;
}
// case MotionEvent.ACTION_MOVE: {
// Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_MOVE");
// return true;
// }
// case MotionEvent.ACTION_UP: {
// Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_UP");
// return true;
// }
}
return false;
}
Log
09-05 07:02:21.223 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-05 07:02:21.223 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-05 07:02:21.223 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-05 07:02:21.223 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-05 07:02:21.223 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
09-05 07:02:21.223 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------: 我消耗了ACTION_DOWN
09-05 07:02:21.315 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-05 07:02:21.315 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-05 07:02:21.315 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-05 07:02:21.315 1444-1444/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
结论:ViewC的方法得不到调用,down事件中onInterceptTouchEvent事件得到调用,而跟他同一个事件序列的up事件就不会再次调用onInterceptTouchEvent,那么up事件谁处理了,答案是被boss activity处理了
boss有 A ,B 两个相关联的任务,把他分两次给经理,当A完成之后才给B,经理A分给两个任务给组长B,一个重要的down,一个次要的其他,两个事件是相关的,(后面会说为什么一个重要的,一个不重要的),组长B觉得自己可以做就将重要的事件拦截下来,自己做,不需要职员C做,并且做完这件重要的事件,但是后面次要的事件组长却没有完成了,经理以为都完成了所以没自己动手,直接给boss,boss一看就自己做了没做的那部分
②不消耗down,其余消耗(move,up)
ViewGroupB
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "ViewGroupB------onTouchEvent: ");
switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN: {
// Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_DOWN");
// return true;
// }
case MotionEvent.ACTION_MOVE: {
Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_MOVE");
return true;
}
case MotionEvent.ACTION_UP: {
Log.d(TAG, "ViewGroupB------: 我消耗了ACTION_UP");
return true;
}
}
return false;
}
ViewGroupA
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "ViewGroupA -----onTouchEvent: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Log.d(TAG, "ViewGroupA------: 我是ACTION_DOWN");
break;
}
case MotionEvent.ACTION_MOVE: {
Log.d(TAG, "ViewGroupA------: 我是ACTION_MOVE");
break;
}
case MotionEvent.ACTION_UP: {
Log.d(TAG, "ViewGroupA------: 我是ACTION_UP");
break;
}
}
boolean flag = super.onTouchEvent(event);
Log.d(TAG, "onTouchEvent: "+flag);
return flag;
}
Log:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA -----onTouchEvent:
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA------: 我是ACTION_DOWN
09-05 07:08:27.959 6960-6960/com.example.jinxiong.viewaction D/ViewGroupA: onTouchEvent: false
结论:可以看到ViewGroupA被调用了,但是这里设置了ViewGroupA并没有消耗任何事件,所以ViewGroupA只是调用了一次,这里不讨论ViewGroupA
boss有 A ,B 两个相关联的任务,把他分两次给经理,当A完成之后才给B,
上面说过为什么经理分给组长B一件重要的事件down,一件次要的up或其他,这次组长也是觉得自己能做完经理给的重要任务,所以就拦截下来了,但是没想到这件重要的事件居然完成不了,好了,经理就只好自己动手,但是经理也是没完成(可以设置为完成(消耗),这里我已没完成做例子),就给回daboss activity ,所以它就来处理了,然后还有一个次要的任务,因为经理都没有完成重要的事情,boss就不给次要人物给经理了,但如果刚刚我们的经历完成了重要的事情,那么boss还是会给次要的事情给经理的,但是经理不会再那么笨给组长B了,因为组长刚刚都没好好完成重要任务那么在这个例子中,因为整体所有的事情都没有人处理,所以boss接手所有的事件处理
从这里我们知道down事件是个很特殊的事件,如果拦截事情但是没处理这个down事件的话,同一个序列的剩下的事件你就没资格处理了,都是给你的上级处理,上一层的ViewGroup(控制树视图的上一层)
③拦截全部,全部不消耗
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-------dispatchTouchEvent:
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA-----onInterceptTouchEvent:
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------dispatchTouchEvent:
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB-----onInterceptTouchEvent:
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupB------onTouchEvent:
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA -----onTouchEvent:
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: ViewGroupA------: 我是ACTION_DOWN
09-05 07:38:19.723 1815-1815/com.example.jinxiong.viewaction D/ViewGroupA: onTouchEvent: false
结论:你把所有拦截下来,影响的只是你的子view ,它永远没有机会得到事件处理的机会,你全部不处理,那么你的父View就会接手,但是父view消耗不消耗这件事就看情况,父view不消耗的话,就往上传,最终还是bossactivity处理。
因为拦截序列的其中一个事件,那么剩下的事件都是默认交给这个拦截的view处理的,所以下面的情况都是跟上面拦截全部事件是一样的
④拦截一部分,消耗全部
⑤拦截一部分,消耗一部分
⑥拦截一部分,全部不消耗
⑦不拦截,有机会消耗
①消耗全部
②消耗一部分,剩下的就会给activity消耗处理
③不消耗 ,全部都是activity消耗
⑧不拦截,没机会消耗
总结:
事件如果被拦截了,如果你只是处理了down事件,那么剩下的up,move 事件就交由activity处理,如果你down事件没有处理,那么这一事件序列将交由你的父view处理,
一个事件序列只能有一个view来消耗,但是你也可以在你当前view的事件处理中通过父/子view 的对象调用他们的onTouchEvent来打破这个规矩,
如果你拦截的只是一个down事件,那么剩下的这一个序列事件的move,up都会交给你这个view处理,并且剩下的事件中机即move,up事件将不会调用到onInterceptTouchEvent这个方法,
ViewGroup默认是不拦截事件的,View中没有onInterceptTouchEvent这个方法的,收到事件后是直接调用OnTouchEvent的,当一个View的clickable/longclickable中其中一个为true,那么就会消耗事件
一个onClick事件是收到down和up事件,但你设置一个clickListener 这个view的clickable就会变为true
事件总是由父view分发给子view,可以通过requestDisallowInterceptTouchView蓝干预父view 的事件分发,但是不能干预down事件