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

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

(0)

相关推荐

  • 详谈Android中onTouch与onClick事件的关系(必看)

    这几天遇到点关于Android的触摸事件相关的,还跟onClick有关,暂且记下: LinearLayout分别设置了onTouchListener,onClickListener,onLongClickListener及onTouchEvent回调 1.在屏幕上触摸之后基本的执行流程如下: onTouch,action=0 onTouchEvent,action=0 onTouch,action=2 onTouchEvent,action=2 onTouch,action=2 onTouchE

  • 三种Android单击事件onclick的实现方法

    onclick事件的定义方法,分为三种,分别为在xml中进行指定方法:在Actitivy中new出一个OnClickListenner():实现OnClickListener接口三种方式. 代码分别如下: 1. xml指定onclick事件,这种方式比较适用于指定的button,能使java代码相对简化一些: xml文件中: <span style="color:#333333;"><Button android:text="Button03" a

  • 详细分析Android中onTouch事件传递机制

    onTach介绍 ontach是Android系统中整个事件机制的基础.Android中的其他事件,如onClick.onLongClick等都是以onTach为基础的. onTach包括从手指按下到离开手机屏幕的整个过程,在微观形式上,具体表现为action_down.action_move和action_up等过程. onTach两种主要定义形式如下: 1.在自定义控件中,常见的有重写onTouchEvent(MotionEvent ev)方法.如在开发中经常可以看到重写的onTouchEv

  • 浅谈java Iterator.remove()方法的用法(详解)

    实例如下: @Test public void tt(){ List<String> list = new ArrayList<String>(); list.add( "0" ); list.add( "1" ); list.add( "2" ); list.add( "3" ); list.add( "4" ); list.add( "5" ); list.a

  • 浅谈Android onTouchEvent 与 onInterceptTouchEvent的区别详解

    首先从字面意思理解两个词 onTouchEvent:触发触摸事件 onInterceptTouchEvent:触发拦截触摸事件 通过查看源代码及类继承关系 onInterceptTouchEvent:是定义于ViewGroup里面的一个方法,此事件是用于拦截触摸事件的,ViewGroup(继承自View),一个View的Group,也就是我们的一个布局如LinerLayout,各个布局类都继承自ViewGroup: onTouchEvent:是定义于View中的一个方法,处理传递到View的手势

  • 浅谈java中静态方法的重写问题详解

    首先来看看以下程序将会打印出什么: 复制代码 代码如下: class Dog {    public static void bark() {        System.out.print("woof ");    }} class Basenji extends Dog {    public static void bark() { }} public class Bark {    public static void main(String args[]) {       

  • 浅谈C#中Process类的使用详解

    Process类的作用是对系统进程进行管理,我们使用Process类中的一些方法结合Winform开发个简单的进程管理器: 在使用Process类的时候,先导入命名空间System.Diagnostics.使用Winform画出如上的界面,数据显示使用的是Listview.在窗体的Load事件中写出如下代码: 复制代码 代码如下: private void Form1_Load(object sender, EventArgs e)    { listView1.FullRowSelect =

  • 浅谈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() { @Ove

  • 浅谈Unity脚本生命周期与执行顺序

    一.脚本生命周期 Unity脚本中的常见必然事件如下表所示 名称 触发时机 用途 Awake 脚本实例被创建时调用 用于游戏对象的初始化,注意Awake的执行早于所有脚本的Start函数 OnEnable 当对象变为可用或激活状态时被调用 用途 Start Update函数第一次运行之前调用 用于游戏对象的初始化 Update 每帧调用一次 用于更新游戏场景和状态 FixedUpdate 每个固定物理时间间隔调用一次 用于物理状态的更新 LateUpdate 每帧调用一次(在update之后调用

  • 浅谈Flink容错机制之作业执行和守护进程

    一.作业执行容错 Flink 的错误恢复机制分为多个级别,即 Execution 级别的 Failover 策略和 ExecutionGraph 级别的 Job Restart 策略.当出现错误时,Flink 会先尝试触发范围小的错误恢复机制,如果仍处理不了才会升级为更大范围的错误恢复机制,具体可以看下面的序列图. 当 Task 发生错误,TaskManager 会通过 RPC 通知 JobManager,后者将对应 Execution 的状态转为 failed 并触发 Failover 策略.

  • MyBatis 执行动态 SQL语句详解

    大家基本上都知道如何使用 MyBatis 执行任意 SQL,使用方法很简单,例如在一个 XXMapper.xml 中: <select id="executeSql" resultType="map"> ${_parameter} </select> 你可以如下调用: sqlSession.selectList("executeSql", "select * from sysuser where enabled

  • SpringMVC的执行流程及组件详解

    这篇文章主要介绍了SpringMVC的执行流程及组件详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.核心模块 数据库访问技术与集成:JDBC.XML等 Web与远程调用技术:SpringMVC.WebServlet.WebSocket等 面向切面编程:AOP 基础设施:Tomcat Spring核心容器:Beans.Core.Context.Expression.ContestSupport 测试:Test 二.执行流程 1.用户通过页

  • 对python_discover方法遍历所有执行的用例详解

    当我们写了一个单个py的测试文件时直接运行就ok了,但当我们有很多很多个这样的py时,难道要一个一个的点击来运行吗,当然不是.我们可以通过discover方法来找到所有的用例. 下面直接举例说明discover用法: 一. 准备工作 目录结构: DiscoverCase.py 文件代码: import unittest import os def discover_case(case_dir): # 待执行用例的目录 testcase = unittest.TestSuite() discove

随机推荐