看了大神们对android事件分发机制的解析,为了方便自己理解和记忆,特意写一篇博客。
目录
方法执行顺序
各方法简单说明
getParent().requestDisallowInterceptTouchEvent(true)
boolean dispatchTouchEvent(MotionEvent ev):事件分发,Activity类,View类都有的方法
boolean onInterceptTouchEvent(MotionEvent ev):事件拦截,只有ViewGroup类才有的方法
boolean onTouch(View v, MotionEvent event):这不是View自带的方法,要通过setOnTouchListener()来添加OnTouchListener然后进入的一个方法
boolean onTouchEvent(MotionEvent event):事件消费,Activity类,View类都有的方法
布局很简单,就一个RelativeLayout里面放个自定义的LinearLayout,LinearLayout里面再放个自定义的Button。自定义了两个控件,因为要重写这两个控件里面的一些方法。
大家都知道事件分发机制分为拦截、传递、消费。根据上面的布局,用文字表达,如一个按下的事件MotionEvent.ACTION_DOWN发生的时候,是RelativeLayout先拿到,然后看这个布局R君要不要拦截,不拦截就传递给LinearLayout布局的L君,L君不拦截就传递到B君,B君如果消费掉这次事件(就是处理了这次事件),那这个按下的事件就到此结束了。如果B君不消费,那这个事件就会回到L君,L君消费,就在L君那结束。L君不消费,事件就回到R君那里处理。
文字表达有点乱,下下面会有流程图。
Activity里就为各控件添加onTouch方法和打印日志。(以下是Activity部分代码,代码用的是ButterKnife)
@OnTouch(R.id.btn_test1)
public boolean onTouch1(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
//btnTest1.getParent().requestDisallowInterceptTouchEvent(true);
// 手指按下
Log.e("ousyxx", "downTest1");
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("ousyxx", "moveTest1");
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("ousyxx", "upTest1");
break;
case MotionEvent.ACTION_CANCEL:
// 事件被拦截
Log.e("ousyxx", "cancelTest1");
break;
}
return false;
}
@OnTouch(R.id.ll_test1)
public boolean onLlTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
// 手指按下
Log.e("ousyxx", "downLlTest");
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("ousyxx", "moveLlTest");
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("ousyxx", "upLlTest");
break;
case MotionEvent.ACTION_CANCEL:
// 事件被拦截
Log.e("ousyxx", "cancelLlTest");
break;
}
return false;
}
@OnTouch(R.id.rl_test1)
public boolean onRlTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
// 手指按下
Log.e("ousyxx", "downRlTest");
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("ousyxx", "moveRlTest");
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("ousyxx", "upRlTest");
break;
case MotionEvent.ACTION_CANCEL:
// 事件被拦截
Log.e("ousyxx", "cancelRlTest");
break;
}
return false;
}
说明之前,大家先看看,假如你没重写过任何方法,就按照上面的布局,你点击那个MyButton控件,大概的事件传递流程如下
dispatchTouchEvent:有事件发生的时候,先进入这个方法。
return false: 事件不会被再进行分发。事件会被传递回上一层的view的onTouch方法、onTouchEvent方法。如果view没有添加onTouchListener,那事件就会直接到onTouchEvent;
return true:该事件就停在这方法里处理,不会继续传递。
举个例子1:加入你在Activity类里重写了dispatchTouchEvent这个方法如下,返回super.dispatchTouchEvent(ev),就是执行你没重写原本Activity类自己原本的dispatchTouchEvent方法,当mTouchType为true时就会进入return true,这时这个Activity上就什么事件都不会发生,什么点击滑动都没有,因为事件在该Activity你重写dispatchTouchEvent里已经结束了这次事件。
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
if (mTouchType)
{
return true;
}
return super.dispatchTouchEvent(ev);
}
举个例子2:下面是我自定义LinearLayout的代码,在dispatchTouchEvent里直接return false,那么事件就会回到R君那里,流程图大概如下
public class MyLinearLayout extends LinearLayout
{
// 是否拦截
private boolean mIsIntercept = false;
public MyLinearLayout(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
if (mIsIntercept)
return true;
return super.onInterceptTouchEvent(ev);
}
}
onInterceptTouchEvent:所有ViewGroup类正常的情况下经过上面的方法后都会进入此拦截方法。 return false:不拦截,事件传递给子view return true: 拦截,事件会在该层处理 上面的代码,我重新写了这个方法,假如上面的代码我没有重写dispatchTouchEvent,而是重写onInterceptTouchEvent,让它return true,流程图就如下
onTouch:控件自己setOnTouchListener添加的方法
return false: 不消费此事件,事件会传到该控件的onTouchEvent方法
return true:消费此事件,事件不会传到该控件的onTouchEvent方法,事件就此结束
onTouchEvent:要不要消费此事件的方法
return false: 不消费此事件,事件就会返回到自己的父View的onTouch继续处理
return true:消费此事件,事件就此结束
举个例子:假如你重写了Button类的onTouchEvent,让它return false,那么流程图大概如下
这个方法在关于事件分发的开发中可能会见到,所以也记录一下。一般大家可能看到L君的onInterceptTouchEvent的拦截方法的重写是如下这样。可以看到,L君只在ACTION_MOVE处才return true,就是说但你按下B君控件时,按下的事件是进入了B君的onTouch1。而当你按下、滑动的时候,滑动事件就被L君拦截,那么滑动事件就会进入L君的onLlTouch。
但从打印的日志可以看到,同时也有个事件进入了B君的onTouch1,那就是MotionEvent.ACTION_CANCEL。没错当B君的事件中途被父View拦截后,就会有ACTION_CANCEL事件进入B君的onTouch1。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
return false;
case MotionEvent.ACTION_MOVE:
return true;
case MotionEvent.ACTION_UP:
return false;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
这时如果B君不想这移动的事件被拦截的话,就可以使用getParent().requestDisallowInterceptTouchEvent(true)请求父View即L君不要拦截B君接下来的事件。可以看看代码如下:
@OnTouch(R.id.btn_test1)
public boolean onTouch1(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
btnTest1.getParent().requestDisallowInterceptTouchEvent(true);
// 手指按下
Log.e("ousyxx", "downTest1");
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("ousyxx", "moveTest1");
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("ousyxx", "upTest1");
break;
case MotionEvent.ACTION_CANCEL:
// 事件被拦截
Log.e("ousyxx", "cancelTest1");
break;
}
return false;
}
如1代码所示,在B君接收到ACTION_DOWN按下事件时执行了请求,那么接下来移动的事件还是会进入到B君的onTouch1了。