android 事件分发机制(转载)
转载自:http://blog.csdn.net/chenzujie/article/details/13277081
直接进入正题,在android开发中,手势触碰是经常使用到的。这两天刚好在看这方面的资料,在这里写篇文章做个小总结。
首先必须知道五大布局Layout:LineaLayout,RelativeLayout,FrameLayout,AbsoluteLayout都继承自ViewGroup,而TableLayout继承LinearLayout,也相当于继承于ViewGroup。
而ViewGroup继承自View。
而我们最终在屏幕上显示控制的控件如TextView,Button等都直接或间接继承自View。
View中有两个方法参与到Touch事件分发
onDispatchTouchEvent(MotionEvent event)和onTouch(MotionEvent event)
ViewGroup有三个方法参与到Touch事件分发
onDispatchTouchEvent(MotionEvent event),onInterceptTouchEvent(MotionEvent ev),onTouch(MotionEvent event)
当我们手指出门屏幕,手势事件最早被传递到Layout布局的dispatchTouchEvent,即ViewGroup的dispatchTouchEvent
[java]view plaincopy
publicbooleandispatchTouchEvent(MotionEventev){
finalintaction=ev.getAction();
finalfloatxf=ev.getX();
finalfloatyf=ev.getY();
finalfloatscrolledXFloat=xf+mScrollX;
finalfloatscrolledYFloat=yf+mScrollY;
finalRectframe=mTempRect;
booleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
if(action==MotionEvent.ACTION_DOWN){
if(mMotionTarget!=null){
mMotionTarget=null;
}
if(disallowIntercept||!onInterceptTouchEvent(ev)){
ev.setAction(MotionEvent.ACTION_DOWN);
finalintscrolledXInt=(int)scrolledXFloat;
finalintscrolledYInt=(int)scrolledYFloat;
finalView[]children=mChildren;
finalintcount=mChildrenCount;
for(inti=count-1;i>=0;i--){
finalViewchild=children[i];
if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE
||child.getAnimation()!=null){
child.getHitRect(frame);
if(frame.contains(scrolledXInt,scrolledYInt)){
finalfloatxc=scrolledXFloat-child.mLeft;
finalfloatyc=scrolledYFloat-child.mTop;
ev.setLocation(xc,yc);
child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
if(child.dispatchTouchEvent(ev)){
mMotionTarget=child;
returntrue;
}
}
}
}
}
}
booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||
(action==MotionEvent.ACTION_CANCEL);
if(isUpOrCancel){
mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;
}
finalViewtarget=mMotionTarget;
if(target==null){
ev.setLocation(xf,yf);
if((mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
}
returnsuper.dispatchTouchEvent(ev);
}
if(!disallowIntercept&&onInterceptTouchEvent(ev)){
finalfloatxc=scrolledXFloat-(float)target.mLeft;
finalfloatyc=scrolledYFloat-(float)target.mTop;
mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc,yc);
if(!target.dispatchTouchEvent(ev)){
}
mMotionTarget=null;
returntrue;
}
if(isUpOrCancel){
mMotionTarget=null;
}
finalfloatxc=scrolledXFloat-(float)target.mLeft;
finalfloatyc=scrolledYFloat-(float)target.mTop;
ev.setLocation(xc,yc);
if((target.mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
mMotionTarget=null;
}
returntarget.dispatchTouchEvent(ev);
}
此段代码叫长,挑重点看,第9行,但是ACTION_DOWN事件时,会进入到第13行的判断,第一个参数disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。而第二个参数!onInterceptTouchEvent(ev)即是否进行拦截的判断,如果我们不复写这个方法,默认为false,就会进入if判断,并获得点击区域有的view,在第29行将手势事件传递给被点击到的view,如果view的dispatchTouchEvent(ev)返回true,表示手势事件被这个view消费了(view的dispatchTouchEvent后续分析),并把该view置给mMotionTarget后返回true。
这里,正是因为有view消费了ACTION_DOWN事件,使得当后续的ACTION_MOVE ACTION_UP传递到这个ViewGroup的时候,mMotionTarget不为空,使得47行的target不为空,最后都会执行74行的target.dispatchTouchEvent(ev),即ACTION_DOWN事件被哪个view的onTouch或者onTouchEvent返回了true,那么后续的ACTION_MOVE,ACTION_UP事件将会直接传递给这个view执行。(onTouch和onTouchEvent后续分析)
继续分析这个ViewGroup的dispatchTouchEvent,如果13行的onInterceptTouchEvent被我们复写返回true代表ACTION_DOWN被这个ViewGroup拦截下来,将不会进入if判断,因此mMotionTarget将为null使得target也为null,最后执行了50行的super.dispatchTouchEvent即ViewGroup的父类View的dispatchTouchEvent。并在其中调用onTouch或者onTouchEvent。
如果我们不复写onInterceptTouchEvent让其进入if判断,但如果child的dispatchTouchEvent返回的是false,表示child没有把这个事件消费了,最终也还是调用了50行的super.dispatchTouchEvent(ev)。
现在再来看看上面分析中经常说到的View的dispatchTouchEvent
[java]view plaincopy
publicbooleandispatchTouchEvent(MotionEventevent){
if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&
mOnTouchListener.onTouch(this,event)){
returntrue;
}
returnonTouchEvent(event);
}
非常简短的几行代码,是不是比看了ViewGroup的dispatchTouchEvent轻松多了!
首先先判断mOnTouchListener是否为空,这个mOnTouchListener是啥呢,看下源码就知道了
[java]view plaincopy
publicvoidsetOnTouchListener(OnTouchListenerl){
mOnTouchListener=l;
}
显然,就是我们设置OnTouchListener的接口实例,并在其中复写了onTouch方法。
if判断的第二个参数用来判断当前view是否是enable为状态。而第三个参数就是我们复写的onTouch方法了,当我们返回了true,这个view的dispatchtouchEvent将返回true,从而使得调用这个dispatchTouchEvent的ViewGroup的dispatchTouchEvent确定了mMotionTarget,确定了消费了ACTION_DOWN事件的view。
而如果我们没有设置OnTouchEvent接口或者onTouch返回false则调用了onTouchEvent。
继续看下view的onTouchEvent
[java]view plaincopy
publicbooleanonTouchEvent(MotionEventevent){
finalintviewFlags=mViewFlags;
if((viewFlags&ENABLED_MASK)==DISABLED){
//Adisabledviewthatisclickablestillconsumesthetouch
//events,itjustdoesn'trespondtothem.
return(((viewFlags&CLICKABLE)==CLICKABLE||
(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));
}
if(mTouchDelegate!=null){
if(mTouchDelegate.onTouchEvent(event)){
returntrue;
}
}
if(((viewFlags&CLICKABLE)==CLICKABLE||
(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){
switch(event.getAction()){
caseMotionEvent.ACTION_UP:
booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;
if((mPrivateFlags&PRESSED)!=0||prepressed){
//takefocusifwedon'thaveitalreadyandweshouldin
//touchmode.
booleanfocusTaken=false;
if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
focusTaken=requestFocus();
}
if(!mHasPerformedLongPress){
//Thisisatap,soremovethelongpresscheck
removeLongPressCallback();
//Onlyperformtakeclickactionsifwewereinthepressedstate
if(!focusTaken){
//UseaRunnableandpostthisratherthancalling
//performClickdirectly.Thisletsothervisualstate
//oftheviewupdatebeforeclickactionsstart.
if(mPerformClick==null){
mPerformClick=newPerformClick();
}
if(!post(mPerformClick)){
performClick();
}
}
}
if(mUnsetPressedState==null){
mUnsetPressedState=newUnsetPressedState();
}
if(prepressed){
mPrivateFlags|=PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
}elseif(!post(mUnsetPressedState)){
//Ifthepostfailed,unpressrightnow
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
caseMotionEvent.ACTION_DOWN:
if(mPendingCheckForTap==null){
mPendingCheckForTap=newCheckForTap();
}
mPrivateFlags|=PREPRESSED;
mHasPerformedLongPress=false;
postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());
break;
caseMotionEvent.ACTION_CANCEL:
mPrivateFlags&=~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
caseMotionEvent.ACTION_MOVE:
finalintx=(int)event.getX();
finalinty=(int)event.getY();
//Belenientaboutmovingoutsideofbuttons
intslop=mTouchSlop;
if((x<0-slop)||(x>=getWidth()+slop)||
(y<0-slop)||(y>=getHeight()+slop)){
//Outsidebutton
removeTapCallback();
if((mPrivateFlags&PRESSED)!=0){
//Removeanyfuturelongpress/tapchecks
removeLongPressCallback();
//Needtoswitchfrompressedtonotpressed
mPrivateFlags&=~PRESSED;
refreshDrawableState();
}
}
break;
}
returntrue;
}
returnfalse;
}
这段代码同样很长,同样挑重点看。
第14行当这个view是可点击的就会进入if判断,否则直接返回false,因此很有可能这个onTouchEvent的代码如果在第14行进入了if判断,很可能里面会有点击事件的执行。我们往下看,第38行执行ACTION_UP手势时,执行的performClick
[java]view plaincopy
publicbooleanperformClick(){
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if(mOnClickListener!=null){
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
returntrue;
}
returnfalse;
}
没错,performClick确实执行了mOnClickListener.onClikc(this);就是执行我们设置的onClick事件。并且在onTouchEvent的89行返回了true。
在这部分分析中我们也发现了一个小秘密,如果我们复写了onTouchListener中的onTouch让它返回false,这样dispatchTouchEvent将不会执行到onTouchEvent方法,使得onClick事件永远得不到执行。
好了,全部分析完毕,做几点小总结
1、onTouch优先于onTouchEvent执行,且onTouch返回true将不执行onTouchEvent,并导致onTouchEvent里的点击事件得不到执行
2、手势事件从最外部的ViewGroup的dispatchTouchEvent开始分发,如果onInterceptTouchEvent拦截了该手势事件,即返回true,手势事件将不会往下分发,即不执行view的dispatchTouchEvent,而是让该ViewGroup的的父类dispatchTouchEvent来消费该手势事件。
3、如果View的dispatchTouchEvent消费了ACTION_DOWN事件,即对ACTION_DOWN返回true,后续的的ACTION_MOVE和ACTION_UP将直接被分发到这个view来消费。
最后感谢Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
两篇文章的作者,我就是在看了这两篇文章后,再看了些资料才了解了android手势分发事件
更多相关文章
- 蓝牙API
- android中锁屏后代码不运行的问题
- Android再按一次返回键退出程序的实现
- Android学习系列(43)--使用事件总线框架EventBus和Otto
- android 异步加载
- Android(安卓)View事件派发机制详解与源码分析
- Android之动态申请存储权限
- Android(安卓)Studio——点击事件监听
- Android(安卓)中文 API (29) —— CompoundButton