Android事件分发机制解析
看了大神们对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:不拦截,事件传递给子viewreturn true: 拦截,事件会在该层处理上面的代码,我重新写了这个方法,假如上面的代码我没有重写dispatchTouchEvent,而是重写onInterceptTouchEvent,让它return true,流程图就如下
onTouch:控件自己setOnTouchListener添加的方法
return false: 不消费此事件,事件会传到该控件的onTouchEvent方法
return true:消费此事件,事件不会传到该控件的onTouchEvent方法,事件就此结束
onTouchEvent:要不要消费此事件的方法
return false: 不消费此事件,事件就会返回到自己的父View的onTouch继续处理
return true:消费此事件,事件就此结束
举个例子:假如你重写了Button类的onTouchEvent,让它return false,那么流程图大概如下
getParent().requestDisallowInterceptTouchEvent(true)
这个方法在关于事件分发的开发中可能会见到,所以也记录一下。一般大家可能看到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了。
完!
更多相关文章
- Android(安卓)ViewDragHelper使用介绍
- Android事件处理(6)
- Android(安卓)Dialog大全
- Android网络编程
- android 屏保锁中屏掉按键和HOME键的方法
- android content provider概述
- Android刘海屏全面屏底部导航栏的适配
- SuperITGirl李小扣 air for android做的flash客户端,退出程序的方
- android检查手机和无线是否连接的方法