onTouch先执行,还是onClick执行?

有一个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)方法源码中,接下来分析一下该函数的源码:

 1 public boolean onTouchEvent(MotionEvent event) {  
 2     final int viewFlags = mViewFlags;  
 3     if ((viewFlags & ENABLED_MASK) == DISABLED) {  
 4         // A disabled view that is clickable still consumes the touch  
 5         // events, it just doesn‘t respond to them.  
 6         return (((viewFlags & CLICKABLE) == CLICKABLE ||  
 7                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
 8     }  
 9     if (mTouchDelegate != null) {  
10         if (mTouchDelegate.onTouchEvent(event)) {  
11             return true;  
12         }  
13     }  
14     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
15             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
16         switch (event.getAction()) {  
17             case MotionEvent.ACTION_UP:  
18                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
19                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
20                     // take focus if we don‘t have it already and we should in  
21                     // touch mode.  
22                     boolean focusTaken = false;  
23                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
24                         focusTaken = requestFocus();  
25                     }  
26                     if (!mHasPerformedLongPress) {  
27                         // This is a tap, so remove the longpress check  
28                         removeLongPressCallback();  
29                         // Only perform take click actions if we were in the pressed state  
30                         if (!focusTaken) {  
31                             // Use a Runnable and post this rather than calling  
32                             // performClick directly. This lets other visual state  
33                             // of the view update before click actions start.  
34                             if (mPerformClick == null) {  
35                                 mPerformClick = new PerformClick();  
36                             }  
37                             if (!post(mPerformClick)) {  
38                                 performClick();  
39                             }  
40                         }  
41                     }  
42                     if (mUnsetPressedState == null) {  
43                         mUnsetPressedState = new UnsetPressedState();  
44                     }  
45                     if (prepressed) {  
46                         mPrivateFlags |= PRESSED;  
47                         refreshDrawableState();  
48                         postDelayed(mUnsetPressedState,  
49                                 ViewConfiguration.getPressedStateDuration());  
50                     } else if (!post(mUnsetPressedState)) {  
51                         // If the post failed, unpress right now  
52                         mUnsetPressedState.run();  
53                     }  
54                     removeTapCallback();  
55                 }  
56                 break;  
57             case MotionEvent.ACTION_DOWN:  
58                 if (mPendingCheckForTap == null) {  
59                     mPendingCheckForTap = new CheckForTap();  
60                 }  
61                 mPrivateFlags |= PREPRESSED;  
62                 mHasPerformedLongPress = false;  
63                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
64                 break;  
65             case MotionEvent.ACTION_CANCEL:  
66                 mPrivateFlags &= ~PRESSED;  
67                 refreshDrawableState();  
68                 removeTapCallback();  
69                 break;  
70             case MotionEvent.ACTION_MOVE:  
71                 final int x = (int) event.getX();  
72                 final int y = (int) event.getY();  
73                 // Be lenient about moving outside of buttons  
74                 int slop = mTouchSlop;  
75                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
76                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
77                     // Outside button  
78                     removeTapCallback();  
79                     if ((mPrivateFlags & PRESSED) != 0) {  
80                         // Remove any future long press/tap checks  
81                         removeLongPressCallback();  
82                         // Need to switch from pressed to not pressed  
83                         mPrivateFlags &= ~PRESSED;  
84                         refreshDrawableState();  
85                     }  
86                 }  
87                 break;  
88         }  
89         return true;  
90     }  
91     return false;  
92 }

  虽然源码有点多,但是我们只重点关注关键代码,在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。如下所示:

  

  

时间: 2024-10-24 20:39:19

onTouch先执行,还是onClick执行?的相关文章

onclick执行两个方法

用分号隔开即可,比如 javascript代码: function a(){   alert(1);   }  function b(){   alert(2);   } html代码:<input type="button" onclick="a();b();" value="ab"/> onclick执行两个方法

自定义控件(视图)2期笔记14:自定义视图之View事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

1. 这里我们先从案例角度说明dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程: (1)首先我们重写一个MyButton 继承自 Button,代码如下: 1 package com.himi.eventdemo; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.util.Log; 6 import andro

从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)

关于Android View 事件分发过程的文章网络上可以搜到一把大,这里贴一篇代码性的文章,作者也是个牛人:Android事件分发机制完全解析,带你从源码的角度彻底理解(上). 虽然讲的很好,但是看完之后还是感觉有那么点一知半解,于是自己花了点时间从源码研究android 触摸事件分发流程,以下内容仅仅个人理解,如有差错希望指出. 我们先从一个例子看起,先重写一个MyButton 继承Button,代码如下: public class MyButton extends Button { pub

使用定时器实现JavaScript的延期执行或重复执行

使用定时器实现JavaScript的延期执行或重复执行 window 对象提供了两个方法来实现定时器的效果,分别是window.setTimeout()和 window.setInterval.其中前者可以使一段代码在指定时间后运行:而后者则可以使一段代码 每过指定时间就运行一次.它们的原型如下: window.setTimeout(expression,milliseconds); window.setInterval(expression,milliseconds); 其中,expressi

同步执行与异步执行

计算机程序执行分为同步执行和异步执行 (1)同步执行 所谓的同步执行,就是正常的计算机程序执行的3大顺序流程: 顺序控制语句:从上至下,从左至右 分支控制语句:if,switch 循环控制语句:for(),while,do...while,for...in,forEach() (2)异步执行 所谓的异步执行,是一种特殊的程序的执行方式: setInterval(定时器),setTimeout(延时器) 事件的绑定 onclick,onopen,onscroll... ajax请求 (3)同步执行

iOS: 零误差或极小误差的定时执行或延迟执行?

问题如下: 节奏类游戏需要执行很多的跟音乐节拍相关的操作,并且为了保证节奏感,需要让操作跟节拍的关系十分紧密.对两者间隔要求不能超过0.02秒或更低. 目前使用了 GCD 中的 asyncAfter(deadline:)方法,不过误差总是要大于0.05秒,并且还无法保证误差会不会传递下去.请问有更好的方式来解决误差吗? var time = Date().timeIntervalSince1970 let dq = DispatchQueue(label: "queue", qos:

java主线程等待所有子线程执行完毕在执行(常见面试题)

java主线程等待所有子线程执行完毕在执行(常见面试题) java主线程等待所有子线程执行完毕在执行,这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个处理都可以用一个线程来执行,所有处理完成了之后才会返回给用户下单成功,下面就说一下我能想到的方法,欢迎大家批评指正: 用sleep方法,让主线程睡眠一段时间,当然这个睡眠时间是主观的时间,是我们自己定的,这个方法不推荐,但是在这里还是写一下,毕竟是解决方法 2.使用Thread的join()等待所有

现有T1、T2、T3三个线程,你怎样保证T2在T1执行完成之后执行,T3在T2执行完后执行?

考察join /**  * 现有T1.T2.T3三个线程,你怎样保证T2在T1执行完成之后执行,T3在T2执行完后执行?  * @author user  *  */ public class Test3 { public static void main(String[] args) throws InterruptedException { Thread T1 = new Thread(new T1()); Thread T2 = new Thread(new T2()); Thread T

loadrunner多场景的串行执行以及定时执行

方法一: 既然是脚本串行执行,那在场景设计中必然是要用多个脚本,要注意的是需要将Scenario Schedule中的Schedule by设置为Group的模式.然后按实际需要依次设置每个脚本的Schedule.要事先计算好每个脚本的整个执行时间,方便定义后续脚本的开始时间(设置Start Group). 方法二: 使用定时任务执行: 首先创建并设置好要跑的个测试场景,再创建一个一个批处理程序按先后顺序调用这几个个场景进行测试,最后通过Windows的定时任务设定批处理的执行时间 写一个批处理