欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

浅谈onTouch先执行,还是onClick执行(详解)

程序员文章站 2022-06-20 22:54:40
有一个button 按钮,要想为该按钮设置onclick事件和ontouch事件 mtestbutton.setonclicklistener(new view....

有一个button 按钮,要想为该按钮设置onclick事件和ontouch事件

mtestbutton.setonclicklistener(new view.onclicklistener() {  
      @override 
      public void onclick(view view) {  
        log.d(tag, "onclick execute");  
      }  
});  
mtestbutton.setontouchlistener(new view.ontouchlistener() {  
      @override 
      public boolean ontouch(view view, motionevent motionevent) {  
        log.d(tag, "ontouch execute, action event " + motionevent.getaction());  
        return false;  
      }  
});  

此时,我们现在分析一下,是ontouch先执行,还是onclick执行,接下来我从framework 源码去探寻一下整个事件的执行流程和原理:

我们知道 button ,textview 等基础控件的基类都是view,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchtouchevent方法。那当我们去点击按钮的时候,就会去调用button类(实际上是基类view)里的dispatchtouchevent方法,所以接下来看view源码中dispatchtouchevent()方法的具体实现:

public boolean dispatchtouchevent(motionevent event) {  
  if (montouchlistener != null && (mviewflags & enabled_mask) == enabled &&  
      montouchlistener.ontouch(this, event)) {  
    return true;  
  }  
  return ontouchevent(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(motionevent event)方法同样也是在view中定义的一个方法,主要是处理传递到view 的手势事件,包括action_down,action_move,action_up,action_cancel四种事件。

接下来我们结合上面的具体例子,来分析一下这个过程,首先会执行dispatchtouchevent(motionevent event) ,所以ontouch方法肯定是早于onclick方法的,如果在ontouch里返回false,就会出现下面的现象:

10-20 18:57:49.670: debug/mainactivity(20153): ontouch execute, action event 0
10-20 18:57:49.715: debug/mainactivity(20153): ontouch execute, action event 1
10-20 18:57:49.715: debug/mainactivity(20153): onclick execute

即先执行了ontouch,再执行了onclick事件,而且ontouch执行了两次,一个是action_down,一个是action_up事件;

如果ontouch里返回true,则出现下面的现象:

10-20 19:01:59.795: debug/mainactivity(21010): ontouch execute, action event 0
10-20 19:01:59.860: debug/mainactivity(21010): ontouch execute, action event 1

结果是onclick事件没有执行了,原因是如果ontouch返回true的话,则dispatchevent(motionevent event)方法直接返回true了,相当于不往下传递事件了,所以onclick不会执行,相反如果ontouch返回false的话(此时会执行onclick方法),则会执行 ontouchevent(motionevent event)方法,由此可以得出这样一个结论,onclick事件的具体调用执行肯定是在ontouchevent(motionevent event)方法源码中,接下来分析一下该函数的源码:

public boolean ontouchevent(motionevent event) { 
  final int viewflags = mviewflags; 
  if ((viewflags & enabled_mask) == disabled) { 
    // a disabled view that is clickable still consumes the touch 
    // events, it just doesn't respond to them. 
    return (((viewflags & clickable) == clickable || 
        (viewflags & long_clickable) == long_clickable)); 
  } 
  if (mtouchdelegate != null) { 
    if (mtouchdelegate.ontouchevent(event)) { 
      return true; 
    } 
  } 
  if (((viewflags & clickable) == clickable || 
      (viewflags & long_clickable) == long_clickable)) { 
    switch (event.getaction()) { 
      case motionevent.action_up: 
        boolean prepressed = (mprivateflags & prepressed) != 0; 
        if ((mprivateflags & pressed) != 0 || prepressed) { 
          // take focus if we don't have it already and we should in 
          // touch mode. 
          boolean focustaken = false; 
          if (isfocusable() && isfocusableintouchmode() && !isfocused()) { 
            focustaken = requestfocus(); 
          } 
          if (!mhasperformedlongpress) { 
            // this is a tap, so remove the longpress check 
            removelongpresscallback(); 
            // only perform take click actions if we were in the pressed state 
            if (!focustaken) { 
              // use a runnable and post this rather than calling 
              // performclick directly. this lets other visual state 
              // of the view update before click actions start. 
              if (mperformclick == null) { 
                mperformclick = new performclick(); 
              } 
              if (!post(mperformclick)) { 
                performclick(); 
              } 
            } 
          } 
          if (munsetpressedstate == null) { 
            munsetpressedstate = new unsetpressedstate(); 
          } 
          if (prepressed) { 
            mprivateflags |= pressed; 
            refreshdrawablestate(); 
            postdelayed(munsetpressedstate, 
                viewconfiguration.getpressedstateduration()); 
          } else if (!post(munsetpressedstate)) { 
            // if the post failed, unpress right now 
            munsetpressedstate.run(); 
          } 
          removetapcallback(); 
        } 
        break; 
      case motionevent.action_down: 
        if (mpendingcheckfortap == null) { 
          mpendingcheckfortap = new checkfortap(); 
        } 
        mprivateflags |= prepressed; 
        mhasperformedlongpress = false; 
        postdelayed(mpendingcheckfortap, viewconfiguration.gettaptimeout()); 
        break; 
      case motionevent.action_cancel: 
        mprivateflags &= ~pressed; 
        refreshdrawablestate(); 
        removetapcallback(); 
        break; 
      case motionevent.action_move: 
        final int x = (int) event.getx(); 
        final int y = (int) event.gety(); 
        // be lenient about moving outside of buttons 
        int slop = mtouchslop; 
        if ((x < 0 - slop) || (x >= getwidth() + slop) || 
            (y < 0 - slop) || (y >= getheight() + slop)) { 
          // outside button 
          removetapcallback(); 
          if ((mprivateflags & pressed) != 0) { 
            // remove any future long press/tap checks 
            removelongpresscallback(); 
            // need to switch from pressed to not pressed 
            mprivateflags &= ~pressed; 
            refreshdrawablestate(); 
          } 
        } 
        break; 
    } 
    return true; 
  } 
  return false; 
}

虽然源码有点多,但是我们只重点关注关键代码,在38行我们看到了代码:performclick();这个方法从名字表义来看就是onclick方法的调用,我们进入到该方法中去看一探究竟,是否执行了onclick方法呢?

public boolean performclick() {  
  sendaccessibilityevent(accessibilityevent.type_view_clicked);  
  if (monclicklistener != null) {  
    playsoundeffect(soundeffectconstants.click);  
    monclicklistener.onclick(this);  
    return true;  
  }  
  return false;  
} 

从上述代码可以看到,只要monclicklistener不是null,就会去调用它的onclick方法,那monclicklistener又是在哪里赋值的呢?经过分析后找到如下方法:

public void setonclicklistener(onclicklistener l) {  
  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执行(详解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。