返回介绍

Android 触摸屏事件派发机制详解与源码分析一 View 篇

发布于 2025-02-28 12:35:43 字数 24939 浏览 0 评论 0 收藏 0

Notice:阅读完该篇之后如果想继续深入阅读 Android 触摸屏事件派发机制详解与源码分析下一篇请点击 《Android 触摸屏事件派发机制详解与源码分析二 ViewGroup 篇》 查看。

1 背景

最近在简书和微博还有 Q 群看见很多人说 Android 自定义控件(View/ViewGroup)如何学习?为啥那么难?其实答案很简单:“基础不牢,地动山摇。”

不扯蛋了,进入正题。就算你不自定义控件,你也必须要了解 Android 控件的触摸屏事件传递机制(之所以说触摸屏是因为该系列以触摸屏的事件机制分析为主,对于类似 TV 设备等的物理事件机制的分析雷同但有区别。哈哈,谁让我之前是做 Android TV BOX 的,悲催!),只有这样才能将你的控件事件运用的如鱼得水。接下来的控件触摸屏事件传递机制分析依据 Android 5.1.1 源码(API 22)。

2 基础实例现象

2-1 例子

从一个例子分析说起吧。如下是一个很简单不过的 Android 实例:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:gravity="center"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:id="@+id/mylayout">
  <Button
    android:id="@+id/my_btn"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="click test"/>
</LinearLayout>
public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
  private LinearLayout mLayout;
  private Button mButton;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    mLayout = (LinearLayout) this.findViewById(R.id.mylayout);
    mButton = (Button) this.findViewById(R.id.my_btn);

    mLayout.setOnTouchListener(this);
    mButton.setOnTouchListener(this);

    mLayout.setOnClickListener(this);
    mButton.setOnClickListener(this);
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v);
    return false;
  }

  @Override
  public void onClick(View v) {
    Log.i(null, "OnClickListener--onClick--"+v);
  }
}

2-2 现象

如上代码很简单,但凡学过几天 Android 的人都能看懂吧。Activity 中有一个 LinearLayout(ViewGroup 的子类,ViewGroup 是 View 的子类)布局,布局中包含一个按钮(View 的子类);然后分别对这两个控件设置了 Touch 与 Click 的监听事件,具体运行结果如下:

  1. 当稳稳的点击 Button 时打印如下:

  2. 当稳稳的点击除过 Button 以外的其他地方时打印如下:

  3. 当收指点击 Button 时按在 Button 上晃动了一下松开后的打印如下:

机智的你看完这个结果指定知道为啥吧?
我们看下 onTouch 和 onClick,从参数都能看出来 onTouch 比 onClick 强大灵活,毕竟多了一个 event 参数。这样 onTouch 里就可以处理 ACTION_DOWN、ACTION_UP、ACTION_MOVE 等等的各种触摸。现在来分析下上面的打印结果;在 1 中,当我们点击 Button 时会先触发 onTouch 事件(之所以打印 action 为 0,1 各一次是因为按下抬起两个触摸动作被触发)然后才触发 onClick 事件;在 2 中也同理类似 1;在 3 中会发现 onTouch 被多次调运后才调运 onClick,是因为手指晃动了,所以触发了 ACTION_DOWN->ACTION_MOVE…->ACTION_UP。

如果你眼睛比较尖你会看见 onTouch 会有一个返回值,而且在上面返回了 false。你可能会疑惑这个返回值有啥效果?那就验证一下吧,我们将上面的 onTouch 返回值改为 ture。如下:

@Override
  public boolean onTouch(View v, MotionEvent event) {
    Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v);
    return true;
  }

再次点击 Button 结果如下:

看见了吧,如果 onTouch 返回 true 则 onClick 不会被调运了。

2-3 总结结论

好了,经过这个简单的实例验证你可以总结发现:

  1. Android 控件的 Listener 事件触发顺序是先触发 onTouch,其次 onClick。
  2. 如果控件的 onTouch 返回 true 将会阻止事件继续传递,返回 false 事件会继续传递。

对于伸手党码农来说其实到这足矣应付常规的 App 事件监听处理使用开发了,但是对于复杂的事件监听处理或者想自定义控件的码农来说这才是刚刚开始,只是个热身。既然这样那就继续喽。。。

3 Android 5.1.1(API 22) View 触摸屏事件传递源码分析

3-1 写在前面的话

其实 Android 源码无论哪个版本对于触摸屏事件的传递机制都类似,这里只是选用了目前最新版本的源码来分析而已。分析 Android View 事件传递机制之前有必要先看下源码的一些关系,如下是几个继承关系图:

怎么样?看了官方这个继承图是不是明白了上面例子中说的 LinearLayout 是 ViewGroup 的子类,ViewGroup 是 View 的子类,Button 是 View 的子类关系呢?其实,在 Android 中所有的控件无非都是 ViewGroup 或者 View 的子类,说高尚点就是所有控件都是 View 的子类。

这里通过继承关系是说明一切控件都是 View,同时 View 与 ViewGroup 又存在一些区别,所以该模块才只单单先分析 View 触摸屏事件传递机制。

3-2 从 View 的 dispatchTouchEvent 方法说起

在 Android 中你只要触摸控件首先都会触发控件的 dispatchTouchEvent 方法(其实这个方法一般都没在具体的控件类中,而在他的父类 View 中),所以我们先来看下 View 的 dispatchTouchEvent 方法,如下:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
      // We don't have focus or no virtual descendant has it, do not handle the event.
      if (!isAccessibilityFocusedViewOrHost()) {
        return false;
      }
      // We have focus and got the event, then use normal event dispatch.
      event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
      mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
      // Defensive cleanup for new gesture
      stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
      //noinspection SimplifiableIfStatement
      ListenerInfo li = mListenerInfo;
      if (li != null && li.mOnTouchListener != null
          && (mViewFlags & ENABLED_MASK) == ENABLED
          && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
      }

      if (!result && onTouchEvent(event)) {
        result = true;
      }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
      mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
        actionMasked == MotionEvent.ACTION_CANCEL ||
        (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
      stopNestedScroll();
    }

    return result;
  }

dispatchTouchEvent 的代码有点长,咱们看重点就可以。前面都是设置一些标记和处理 input 与手势等传递,到 24 行的

if (onFilterTouchEventForSecurity(event)) 语句判断当前 View 是否没被遮住等,接着 26 行定义 ListenerInfo 局部变量,ListenerInfo 是 View 的静态

内部类,用来定义一堆关于 View 的 XXXListener 等方法;接着 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) ==

ENABLED && li.mOnTouchListener.onTouch(this, event)) 语句就是重点,首先 li 对象自然不会为 null,li.mOnTouchListener 呢?你会发现 ListenerInfo

的 mOnTouchListener 成员是在哪儿赋值的呢?怎么确认他是不是 null 呢?通过在 View 类里搜索可以看到:

/**
   * Register a callback to be invoked when a touch event is sent to this view.
   * @param l the touch listener to attach to this view
   */
  public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l;
  }

li.mOnTouchListener 是不是 null 取决于控件(View)是否设置 setOnTouchListener 监听,在上面的实例中我们是设置过 Button 的 setOnTouchListener 方法的,所以也不为 null;接着通过位与运算确定控件(View)是不是 ENABLED 的,默认控件都是 ENABLED 的;

接着判断 onTouch 的返回值是不是 true。通过如上判断之后如果都为 true 则设置默认为 false 的 result 为 true,那么接下来的 if (!result && onTouchEvent(event)) 就不会执行,最终 dispatchTouchEvent 也会返回 true。而如果 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) 语句有一个为 false 则 if (!result && onTouchEvent(event)) 就会执行,如果 onTouchEvent(event) 返回 false 则 dispatchTouchEvent 返回 false,否则返回 true。

这下再看前面的实例部分明白了吧?控件触摸就会调运 dispatchTouchEvent 方法,而在 dispatchTouchEvent 中先执行的是 onTouch 方法,所以验证了实例结论总结中的 onTouch 优先于 onClick 执行道理。如果控件是 ENABLE 且在 onTouch 方法里返回了 true 则 dispatchTouchEvent 方法也返回 true,不会再继续往下执行;反之,onTouch 返回 false 则会继续向下执行 onTouchEvent 方法,且 dispatchTouchEvent 的返回值与 onTouchEvent 返回值相同。

所以依据这个结论和上面实例打印结果你指定已经大胆猜测认为 onClick 一定与 onTouchEvent 有关系?是不是呢?先告诉你,是的。下面我们会分析。

3-2-1 总结结论

在 View 的触摸屏传递机制中通过分析 dispatchTouchEvent 方法源码我们会得出如下基本结论:

  1. 触摸控件(View)首先执行 dispatchTouchEvent 方法。
  2. 在 dispatchTouchEvent 方法中先执行 onTouch 方法,后执行 onClick 方法(onClick 方法在 onTouchEvent 中执行,下面会分析)。
  3. 如果控件(View)的 onTouch 返回 false 或者 mOnTouchListener 为 null(控件没有设置 setOnTouchListener 方法)或者控件不是 enable 的情况下会调运 onTouchEvent,dispatchTouchEvent 返回值与 onTouchEvent 返回一样。
  4. 如果控件不是 enable 的设置了 onTouch 方法也不会执行,只能通过重写控件的 onTouchEvent 方法处理(上面已经处理分析了),dispatchTouchEvent 返回值与 onTouchEvent 返回一样。
  5. 如果控件(View)是 enable 且 onTouch 返回 true 情况下,dispatchTouchEvent 直接返回 true,不会调用 onTouchEvent 方法。

上面说了 onClick 一定与 onTouchEvent 有关系,那么接下来就分析分析 dispatchTouchEvent 方法中的 onTouchEvent 方法。

3-3 继续说说 View 的 dispatchTouchEvent 方法中调运的 onTouchEvent 方法

上面说了 dispatchTouchEvent 方法中如果 onTouch 返回 false 或者 mOnTouchListener 为 null(控件没有设置 setOnTouchListener 方法)或者控件不是 enable 的情况下会调运 onTouchEvent,所以接着看就知道了,如下:

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
      if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
      }
      // 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 & PFLAG_PREPRESSED) != 0;
          if ((mPrivateFlags & PFLAG_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 (prepressed) {
              // The button is being released before we actually
              // showed it as pressed.  Make it show the pressed
              // state now (before scheduling the click) to ensure
              // the user sees it.
              setPressed(true, x, y);
             }

            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) {
              postDelayed(mUnsetPressedState,
                  ViewConfiguration.getPressedStateDuration());
            } else if (!post(mUnsetPressedState)) {
              // If the post failed, unpress right now
              mUnsetPressedState.run();
            }

            removeTapCallback();
          }
          break;

        case MotionEvent.ACTION_DOWN:
          mHasPerformedLongPress = false;

          if (performButtonActionOnTouchDown(event)) {
            break;
          }

          // Walk up the hierarchy to determine if we're inside a scrolling container.
          boolean isInScrollingContainer = isInScrollingContainer();

          // For views inside a scrolling container, delay the pressed feedback for
          // a short period in case this is a scroll.
          if (isInScrollingContainer) {
            mPrivateFlags |= PFLAG_PREPRESSED;
            if (mPendingCheckForTap == null) {
              mPendingCheckForTap = new CheckForTap();
            }
            mPendingCheckForTap.x = event.getX();
            mPendingCheckForTap.y = event.getY();
            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
          } else {
            // Not inside a scrolling container, so show the feedback right away
            setPressed(true, x, y);
            checkForLongClick(0);
          }
          break;

        case MotionEvent.ACTION_CANCEL:
          setPressed(false);
          removeTapCallback();
          removeLongPressCallback();
          break;

        case MotionEvent.ACTION_MOVE:
          drawableHotspotChanged(x, y);

          // Be lenient about moving outside of buttons
          if (!pointInView(x, y, mTouchSlop)) {
            // Outside button
            removeTapCallback();
            if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
              // Remove any future long press/tap checks
              removeLongPressCallback();

              setPressed(false);
            }
          }
          break;
      }

      return true;
    }

    return false;
  }

我勒个去!一个方法比一个方法代码多。好吧,那咱们继续只挑重点来说明呗。

首先地 6 到 14 行可以看出,如果控件(View)是 disenable 状态,同时是可以 clickable 的则 onTouchEvent 直接消费事件返回 true,反之如果控件(View)是 disenable 状态,同时是 disclickable 的则 onTouchEvent 直接 false。多说一句,关于控件的 enable 或者 clickable 属性可以通过 java 或者 xml 直接设置,每个 view 都有这些属性。

接着 22 行可以看见,如果一个控件是 enable 且 disclickable 则 onTouchEvent 直接返回 false 了;反之,如果一个控件是 enable 且 clickable 则继续进入过于一个 event 的 switch 判断中,然后最终 onTouchEvent 都返回了 true。switch 的 ACTION_DOWN 与 ACTION_MOVE 都进行了一些必要的设置与置位,接着到手抬起来 ACTION_UP 时你会发现,首先判断了是否按下过,同时是不是可以得到焦点,然后尝试获取焦点,然后判断如果不是 longPressed 则通过 post 在 UI Thread 中执行一个 PerformClick 的 Runnable,也就是 performClick 方法。具体如下:

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
      playSoundEffect(SoundEffectConstants.CLICK);
      li.mOnClickListener.onClick(this);
      result = true;
    } else {
      result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
  }

这个方法也是先定义一个 ListenerInfo 的变量然后赋值,接着判断 li.mOnClickListener 是不是为 null,决定执行不执行 onClick。你指定现在已经很

机智了,和 onTouch 一样,搜一下 mOnClickListener 在哪赋值的呗,结果发现:

public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
      setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
  }

看见了吧!控件只要监听了 onClick 方法则 mOnClickListener 就不为 null,而且有意思的是如果调运 setOnClickListener 方法设置监听且控件是

disclickable 的情况下默认会帮设置为 clickable。

我勒个去!!!惊讶吧!!!猜的没错 onClick 就在 onTouchEvent 中执行的,而且是在 onTouchEvent 的 ACTION_UP 事件中执行的。

3-3-1 总结结论

  1. onTouchEvent 方法中会在 ACTION_UP 分支中触发 onClick 的监听。
  2. 当 dispatchTouchEvent 在进行事件分发的时候,只有前一个 action 返回 true,才会触发下一个 action。

到此上面例子中关于 Button 点击的各种打印的真实原因都找到了可靠的证据,也就是说 View 的触摸屏事件传递机制其实也就这么回事。

4 透过源码继续进阶实例验证

其实上面分析完 View 的触摸传递机制之后已经足够用了。如下的实例验证可以说是加深阅读源码的理解,还有一个主要作用就是为将来自定义控件打下坚实基础。因为自定义控件中时常会与这几个方法打交道。

4-1 例子

我们自定义一个 Button(Button 实质继承自 View),如下:

public class TestButton extends Button {

  public TestButton(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    return super.dispatchTouchEvent(event);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action="+event.getAction());
    return super.onTouchEvent(event);
  }
}

其他代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:gravity="center"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:id="@+id/mylayout">
  <com.zzci.light.TestButton
    android:id="@+id/my_btn"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="click test"/>
</LinearLayout>
public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
  private LinearLayout mLayout;
  private TestButton mButton;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    mLayout = (LinearLayout) this.findViewById(R.id.mylayout);
    mButton = (TestButton) this.findViewById(R.id.my_btn);

    mLayout.setOnTouchListener(this);
    mButton.setOnTouchListener(this);

    mLayout.setOnClickListener(this);
    mButton.setOnClickListener(this);
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v);
    return false;
  }

  @Override
  public void onClick(View v) {
    Log.i(null, "OnClickListener--onClick--"+v);
  }
}

其实这段代码只是对上面例子中的 Button 换为了自定义 Button 而已。

4-2 现象分析

4-2-1 点击 Button(手抽筋了一下)

可以发现,如上打印完全符合源码分析结果,dispatchTouchEvent 方法先派发 down 事件,完事调运 onTouch,完事调运 onTouchEvent 返回 true,同时 dispatchTouchEvent 返回 true,然后 dispatchTouchEvent 继续派发 move 或者 up 事件,循环,直到 onTouchEvent 处理 up 事件时调运 onClick 事件,完事返回 true,同时 dispatchTouchEvent 返回 true;一次完整的 View 事件派发流程结束。

4-2-2 简单修改 onTouchEvent 返回值为 true

将 TestButton 类的 onTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action="+event.getAction());
    return true;
  }

点击 Button 打印如下:

可以发现,当自定义了控件(View)的 onTouchEvent 直接返回 true 而不调运 super 方法时,事件派发机制如同 4.2.1 类似,只是最后 up 事件没有触发 onClick 而已(因为没有调用 super)。

所以可想而知,如果 TestButton 类的 onTouchEvent 修改为如下:

@Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action="+event.getAction());
    super.onTouchEvent(event);
    return true;
  }

点击 Button 如下:

整个派发机制和 4.2.1 完全类似。

4-2-3 简单修改 onTouchEvent 返回值为 false

将 TestButton 类的 onTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action="+event.getAction());
    return false;
  }

点击 Button 如下:

你会发现如果 onTouchEvent 返回 false(也即 dispatchTouchEvent 一旦返回 false 将不再继续派发其他 action,立即停止派发),这里只派发了 down 事件。至于后面触发了 LinearLayout 的 touch 与 click 事件我们这里不做关注,下一篇博客会详细解释为啥(其实你可以想下的,LinearLayout 是 ViewGroup 的子类,你懂的),这里你只用知道 View 的 onTouchEvent 返回 false 会阻止继续派发事件。

同理修改如下:

@Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action="+event.getAction());
    super.onTouchEvent(event);
    return false;
  }

点击 Button 如下:

4-2-4 简单修改 dispatchTouchEvent 返回值为 true

将 TestButton 类的 dispatchTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    return true;
  }

点击 Button 如下:

你会发现如果 dispatchTouchEvent 直接返回 true 且不调运 super 任何事件都得不到触发。

继续修改如下呢? 将 TestButton 类的 dispatchTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    super.dispatchTouchEvent(event);
    return true;
  }

点击 Button 如下:

可以发现所有事件都可以得到正常派发,和 4.2.1 类似。

4-2-5 简单修改 dispatchTouchEvent 返回值为 false

将 TestButton 类的 dispatchTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    return false;
  }

点击 Button 如下:

你会发现事件不进行任何继续触发,关于点击 Button 触发了 LinearLayout 的事件暂时不用关注,下篇详解。

继续修改如下呢? 将 TestButton 类的 dispatchTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    super.dispatchTouchEvent(event);
    return false;
  }

点击 Button 如下:

你会发现结果和 4.2.3 的第二部分结果一样,也就是说如果 dispatchTouchEvent 返回 false 事件将不再继续派发下一次。

4-2-6 简单修改 dispatchTouchEvent 与 onTouchEvent 返回值

修改 dispatchTouchEvent 返回值为 true,onTouchEvent 为 false:

将 TestButton 类的 dispatchTouchEvent 方法和 onTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    super.dispatchTouchEvent(event);
    return true;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action=" + event.getAction());
    super.onTouchEvent(event);
    return false;
  }

点击 Button 如下:

修改 dispatchTouchEvent 返回值为 false,onTouchEvent 为 true:

将 TestButton 类的 dispatchTouchEvent 方法和 onTouchEvent 方法修改如下,其他和基础代码保持不变:

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
    Log.i(null, "dispatchTouchEvent-- action=" + event.getAction());
    super.dispatchTouchEvent(event);
    return false;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    Log.i(null, "onTouchEvent-- action=" + event.getAction());
    super.onTouchEvent(event);
    return true;
  }

点击 Button 如下:

由此对比得出结论,dispatchTouchEvent 事件派发是传递的,如果返回值为 false 将停止下次事件派发,如果返回 true 将继续下次派发。譬如,当前派发 down 事件,如果返回 true 则继续派发 up,如果返回 false 派发完 down 就停止了。

4-1 总结

这个例子组合了很多种情况的值去验证上面源码的分析,同时也为自定义控件打下了基础。仔细理解这个例子对于 View 的事件传递就差不多了。

5 总结 View 触摸屏事件传递机制

上面例子也测试了,源码也分析了,总得有个最终结论方便平时写代码作为参考依据呀,不能每次都再去分析一遍源码,那得多蛋疼呢!

综合得出 Android View 的触摸屏事件传递机制有如下特征:

  1. 触摸控件(View)首先执行 dispatchTouchEvent 方法。
  2. 在 dispatchTouchEvent 方法中先执行 onTouch 方法,后执行 onClick 方法(onClick 方法在 onTouchEvent 中执行,下面会分析)。
  3. 如果控件(View)的 onTouch 返回 false 或者 mOnTouchListener 为 null(控件没有设置 setOnTouchListener 方法)或者控件不是 enable 的情况下会调运 onTouchEvent,dispatchTouchEvent 返回值与 onTouchEvent 返回一样。
  4. 如果控件不是 enable 的设置了 onTouch 方法也不会执行,只能通过重写控件的 onTouchEvent 方法处理(上面已经处理分析了),dispatchTouchEvent 返回值与 onTouchEvent 返回一样。
  5. 如果控件(View)是 enable 且 onTouch 返回 true 情况下,dispatchTouchEvent 直接返回 true,不会调用 onTouchEvent 方法。
  6. 当 dispatchTouchEvent 在进行事件分发的时候,只有前一个 action 返回 true,才会触发下一个 action(也就是说 dispatchTouchEvent 返回 true 才会进行下一次 action 派发)。

关于上面的疑惑还有 ViewGroup 事件派发机制你可以继续阅读下一篇博客 《Android 触摸屏事件派发机制详解与源码分析二 ViewGroup 篇》 ,以便继续分析 View 之外的 ViewGroup 事件传递机制。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文