浅谈onTouch先执行,还是onClick执行(详解)
有一个Button按钮,要想为该按钮设置onClick事件和OnTouch事件
mTestButton.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Log.d(TAG,"onClickexecute");
}
});
mTestButton.setOnTouchListener(newView.OnTouchListener(){
@Override
publicbooleanonTouch(Viewview,MotionEventmotionEvent){
Log.d(TAG,"onTouchexecute,actionevent"+motionEvent.getAction());
returnfalse;
}
});
此时,我们现在分析一下,是onTouch先执行,还是onClick执行,接下来我从FrameWork源码去探寻一下整个事件的执行流程和原理:
我们知道Button,TextView等基础控件的基类都是View,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类(实际上是基类View)里的dispatchTouchEvent方法,所以接下来看View源码中dispatchTouchEvent()方法的具体实现:
publicbooleandispatchTouchEvent(MotionEventevent){
if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&
mOnTouchListener.onTouch(this,event)){
returntrue;
}
returnonTouchEvent(event);
}
分析上述代码,第2行如果三个条件都为真的话,就返回true,否则执行onTouchEvent,先看第一个条件mOnTouchListener!=null,这个条件就是如果设置了OnTouchListener就会为true,否则是false;第二个条件(mViewFlags&ENABLED_MASK)==ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true;第三个条件就比较复杂了,mOnTouchListener.onTouch(this,event),这个其实就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。onTouchEvent(MotionEventevent)方法同样也是在view中定义的一个方法,主要是处理传递到view的手势事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL四种事件。
接下来我们结合上面的具体例子,来分析一下这个过程,首先会执行dispatchTouchEvent(MotionEventevent),所以onTouch方法肯定是早于onClick方法的,如果在onTouch里返回false,就会出现下面的现象:
10-2018:57:49.670:DEBUG/MainActivity(20153):onTouchexecute,actionevent0
10-2018:57:49.715:DEBUG/MainActivity(20153):onTouchexecute,actionevent1
10-2018:57:49.715:DEBUG/MainActivity(20153):onClickexecute
即先执行了onTouch,再执行了onClick事件,而且onTouch执行了两次,一个是action_down,一个是action_up事件;
如果onTouch里返回true,则出现下面的现象:
10-2019:01:59.795:DEBUG/MainActivity(21010):onTouchexecute,actionevent0
10-2019:01:59.860:DEBUG/MainActivity(21010):onTouchexecute,actionevent1
结果是onClick事件没有执行了,原因是如果onTouch返回true的话,则dispatchEvent(MotionEventevent)方法直接返回true了,相当于不往下传递事件了,所以onClick不会执行,相反如果onTouch返回false的话(此时会执行onClick方法),则会执行onTouchEvent(MotionEventevent)方法,由此可以得出这样一个结论,onClick事件的具体调用执行肯定是在onTouchEvent(MotionEventevent)方法源码中,接下来分析一下该函数的源码:
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;
}
虽然源码有点多,但是我们只重点关注关键代码,在38行我们看到了代码:performClick();这个方法从名字表义来看就是OnClick方法的调用,我们进入到该方法中去看一探究竟,是否执行了OnClick方法呢?
publicbooleanperformClick(){
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if(mOnClickListener!=null){
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
returntrue;
}
returnfalse;
}
从上述代码可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过分析后找到如下方法:
publicvoidsetOnClickListener(OnClickListenerl){
if(!isClickable()){
setClickable(true);
}
mOnClickListener=l;
}
而上述这个方法就是我们在Application层经常使用的方法,即我们给button设置点击事件的时候就会调用该方法了,分析到这了,我们知道了OnClick方法确实是在OnTouchEvent方法中,那么除了要设置OnClickListener,调用onClick的条件又是什么呢?我们从38行代码往前推,从第14行可以分析出:
只要该控件是可点击的或者是长按类型的,则会进入到MotionEvent.ACTION_UP这个分支当中,然后经过各种条件判断,则会进入到38行的performClick()方法中。
至此,一切都清晰明白了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时或者长按时,都会在performClick()方法里回调被点击控件的onClick方法。
经验之谈:
关于OnTouchEvent(MotionEvent事件)事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。
以上这篇浅谈onTouch先执行,还是onClick执行(详解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。