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

onTach介绍

ontach是Android系统中整个事件机制的基础。Android中的其他事件,如onClick、onLongClick等都是以onTach为基础的。

onTach包括从手指按下到离开手机屏幕的整个过程,在微观形式上,具体表现为action_down、action_move和action_up等过程。

onTach两种主要定义形式如下:

1.在自定义控件中,常见的有重写onTouchEvent(MotionEvent ev)方法。如在开发中经常可以看到重写的onTouchEvent方法,

并且其中有针对不同的微观表现(action_down、action_move和action_up等)做出的相应判断,执行逻辑并可能返回不同的布尔值。

2.在代码中,直接对现有控件设置setOnTouchListener监听器。并重写监听器的onTouch方法。onTouch回调函数中有view和MotionEvent

onTouch事件传递机制

大家都知道一般我们使用的UI控件都是继承自共同的父类——View。所以View这个类应该掌管着onTouch事件的相关处理。那就让我们去看看:在View中寻找Touch相关的方法,其中一个很容易地引起了我们的注意: dispatchTouchEvent(MotionEvent event)

根据方法名的意思应该是负责分发触摸事件的,下面给出了源码:

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
 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的返回值是boolean类型的,注释上的解释:@return True if the event was handled by the view, false otherwise.也就是说如果该触摸事件被这个View消费了就返回true,否则返回false。在方法中首先判断了该event是否是否得到了焦点,如果没有得到焦点直接返回false。然后让我们把目光转向if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event))这个片段,看到这里有一个名为li的局部变量,属于 ListenerInfo 类,经 mListenerInfo 赋值得到。ListenerInfo只是一个包装类,里面封装了大量的监听器。

再在 View 类中去寻找 mListenerInfo ,可以看到下面的代码:

ListenerInfo getListenerInfo() {
 if (mListenerInfo != null) {
 return mListenerInfo;
 }
 mListenerInfo = new ListenerInfo();
 return mListenerInfo;
}

因此我们可以知道mListenerInfo是不为空的,所以li也不是空,第一个判断为true,然后看到li.mOnTouchListener,前面说过ListenerInfo是一个监听器的封装类,所以我们同样去追踪mOnTouchListener:

/**
 * 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;
}

正是通过上面的方法来设置 mOnTouchListener 的,我想上面的方法大家肯定都很熟悉吧,正是我们平时经常用的 xxx.setOnTouchListener ,好了我们从中得知如果设置了OnTouchListener则第二个判断也为true,第三个判断为如果该View是否为enable,默认都是enable的,所以同样为true。还剩最后一个:li.mOnTouchListener.onTouch(this, event) ,显然是回调了第二个判断中监听器的onTouch()方法,如果onTouch()方法返回true,则上面四个判断全部为true,dispatchTouchEvent()方法会返回true,并且不会执行if (!result && onTouchEvent(event))这个判断;而在这个判断中我们又看到了一个熟悉的方法:onTouchEvent() 。所以想要执行onTouchEvent,则在上面的四个判断中必须至少有一个false。

那就假定我们在onTouch()方法中返回的是false,这样就顺利地执行了onTouchEvent,那就看看onTouchEvent的源码吧:

/**
 * Implement this method to handle touch screen motion events.
 * <p>
 * If this method is used to detect click actions, it is recommended that
 * the actions be performed by implementing and calling
 * {@link #performClick()}. This will ensure consistent system behavior,
 * including:
 * <ul>
 * <li>obeying click sound preferences
 * <li>dispatching OnClickListener calls
 * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
 * accessibility features are enabled
 * </ul>
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
 final float x = event.getX();
 final float y = event.getY();
 final int viewFlags = mViewFlags;
 final int action = event.getAction();

 if ((viewFlags & ENABLED_MASK) == DISABLED) {
 if (action == 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)
  || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
 }

 if (mTouchDelegate != null) {
 if (mTouchDelegate.onTouchEvent(event)) {
  return true;
 }
 }

 if (((viewFlags & CLICKABLE) == CLICKABLE ||
  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
  (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
 switch (action) {
  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 && !mIgnoreNextUpEvent) {
   // 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();
  }
  mIgnoreNextUpEvent = false;
  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();
  mInContextButtonPress = false;
  mHasPerformedLongPress = false;
  mIgnoreNextUpEvent = false;
  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;
}

这段源码比 dispatchTouchEvent 的还要长,不过同样我们挑重点的看:
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)
看到这句话就大概知道了主要是判断该view是否是可点击的,如果可以点击则接着执行,否则直接返回false。可以看到if里面用switch来判断是哪种触摸事件,但在最后都是返回true的。

还有一点要注意:在 ACTION_UP 中会执行 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;
}

可以看到上面的li.mOnClickListener.onClick(this); ,没错,我们好像又有了新的发现。根据上面的经验,这句代码会去回调我们设置好的点击事件监听器。也就是我们平常用的xxx.setOnClickListener(listener);

/**
 * Register a callback to be invoked when this view is clicked. If this view is not
 * clickable, it becomes clickable.
 *
 * @param l The callback that will run
 *
 * @see #setClickable(boolean)
 */
public void setOnClickListener(@Nullable OnClickListener l) {
 if (!isClickable()) {
 setClickable(true);
 }
 getListenerInfo().mOnClickListener = l;
}

我们可以看到上面方法设置正是mListenerInfo的点击监听器,验证了上面的猜想。到了这里onTouch事件的传递机制基本已经分析完成了,也算是告一段落了。

好了,这下我们可以解决开头的问题了,顺便我们再来小结一下:在dispatchTouchEvent中,如果设置了OnTouchListener并且View是enable的,那么首先被执行的是OnTouchListener中的onTouch(View v, MotionEvent event) 。若onTouch返回true,则dispatchTouchEvent不再往下执行并且返回true;不然会执行onTouchEvent,在onTouchEvent中若View是可点击的,则返回true,不然为false。还有在onTouchEvent中若View是可点击以及当前触摸事件为ACTION_UP,会执行performClick() ,回调OnClickListener的onClick方法。

下面是我画的一张草图:

还有一点值得注意的地方是:假如当前事件是ACTION_DOWN,只有dispatchTouchEvent返回true了之后该View才会接收到接下来的ACTION_MOVE,ACTION_UP事件,也就是说只有事件被消费了才能接收接下来的事件。

总结

以上就是关于Android中onTouch事件传递机制的详细分析,希望对各位Android开发者们的学习或者工作能有一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • Android事件的分发机制详解

    在分析Android事件分发机制前,明确android的两大基础控件类型:View和ViewGroup.View即普通的控件,没有子布局的,如Button.TextView. ViewGroup继承自View,表示可以有子控件,如Linearlayout.Listview这些.今天我们先来了解View的事件分发机制. 先看下代码,非常简单,只有一个Button,分别给它注册了OnClick和OnTouch的点击事件. btn.setOnClickListener(new View.OnClick

  • 详解Android的两种事件处理机制

    UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于监听器的事件处理. 对于基于监听器的事件处理而言,主要就是为Android界面组件绑定特定的事件监听器:对于基于回调的事件处理而言,主要做法是重写Android组件特定的回调函数,Android大部分界面组件都提供了事件响应的回调函数,我们主要重写它们就行. 一 基于监听器的事件处理 相比于基于回调的事件处理,这是更具"面向对象"性质的事件处理方式.在监听器模型中,主要涉及三类对象

  • Android View 事件分发机制详解

    Android开发,触控无处不在.对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑.View事件的分发机制,不仅在做业务需求中会碰到这些问题,在一些面试笔试题中也常有人问,可谓是老生常谈了.我以前也看过很多人写的这方面的文章,不是说的太啰嗦就是太模糊,还有一些在细节上写的也有争议,故再次重新整理一下这块内容,十分钟让你搞明白View事件的分发机制. 说白了这些触控的事件分发机制就是弄清楚三个方法,dispatchTouchEvent(),OnInterceptTouchEvent(),o

  • 深入解析Android中的事件传递

    前言 前段时间工作中遇到了一个问题,即在软键盘弹出后想监听back事件,但是在Activity中重写了对应的onKeyDown函数却怎么也监听不到,经过一阵Google之后才发现需要重写View的dispatchKeyEventPreIme函数才行.当时就觉得这个函数名字很熟悉,仔细思索一番以后才恍然大悟,当初看WMS源码的时候有过这方面的了解,现在却把它忘到了九霄云外,于是决定写这篇文章,权当记录. InputManagerService 首先我们知道,不论是"键盘事件"还是&quo

  • 老生常谈android中的事件传递和处理机制

    一直以来,都被android中的事件传递和处理机制深深的困扰!今天特意来好好的探讨一下.现在的感觉是,只要你理解到位,其实事件的 传递和处理机制并没有想象中的那么难.总之,不要自己打击自己,要相信自己能掌握这块知识.好了,下面是我今天的收获,希望也 能对你有一点帮助. 一.拟人化来理解android中的事件机制 其实android中的事件传递与处理机制跟我们生活中的事件处理是一样的.这里有一个生活中的例子,很能说明这个问题.阐述如下: 你是一个公司的员工,你的上头有一个主管,主管上头呢还有一个经

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

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

  • 聊聊Android中的事件分发机制

    View事件分发机制的本质就是就是MotionEvent事件的分发过程,即MotionEvent产生后是怎样在View之间传递及处理的. 首先介绍一下什么是MotionEvent.所谓MotionEvent,即用户手指触碰手机屏幕时产生的一系列触摸事件.典型的触摸事件有: ACTION_DOWN:手指刚接触屏幕的一瞬间. ACTION_MOVE:手指在屏幕上滑动. ACTION_UP:手指离开屏幕的一瞬间. ACTION_CANCLE:当前事件序列终止. 一个事件序列一般都是以DOWN事件开始,

  • 详细分析Android中实现Zygote的源码

    概述 在Android系统中,所有的应用程序进程,以及用来运行系统关键服务的System进程都是由zygote进程负责创建的.因此,我们将它称为进程孵化器.zygote进程是通过复制自身的方式来创建System进程和应用程序进程的.由于zygote进程在启动时会在内部创建一个虚拟机实例,因此,通过复制zygote进程而得到的System进程和应用程序进程可以快速地在内部获得一个虚拟机实例拷贝. zygote进程在启动完成之后,会马上将System进程启动起来,以便它可以将系统的关键服务启动起来.

  • Android事件传递机制

    实验环境 OS X 10.9 Eclipse(ADT) Android源码版本:API Level 19(Android 4.4) Android事件构成 在Android中,事件主要包括点按.长按.拖拽.滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作.所有这些都构成了Android中的事件响应.总的来说,所有的事件都由如下三个部分作为基础: 按下(ACTION_DOWN) 移动(ACTION_MOVE) 抬起(ACTION_UP) 所有的操作事件首先必须执行的是按下操作(ACTIO

  • Android触摸事件传递机制初识

    前言 今天总结的一个知识点是Andorid中View事件传递机制,也是核心知识点,相信很多开发者在面对这个问题时候会觉得困惑,另外,View的另外一个难题滑动冲突,比如在ScrollView中嵌套ListView,都是上下滑动,这该如何解决呢,它解决的依据就是View事件的传递机制,所以开发者需要对View的事件传递机制有较深入的理解. 目录 Activity.View.ViewGroup三者关系 触摸事件类型 事件传递三个阶段 View事件传递机制 ViewGroup事件传递机制 小结 Act

  • Android触摸事件传递机制

    前言:在Android开发中,经常会遇到触摸事件冲突,比如ViewPager的轮播图跟Fragment的划动事件冲突,或者轮播图跟下拉事件冲突,自定义view的事件处理等,本文章将会详细介绍Activity.View.ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发.拦截.消费. 本文章将会详细介绍Activity.View.ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发.拦截.消费. 一.触摸事件的类型 触摸事件对应的是 MotionEvent 类,事件类型主

  • 详谈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中线程和线程池

    目录 前言 HandlerThread IntentService 线程池的好处 ThreadPoolExecutor 线程池的分类 FixedThreadPool CachedThreadPool ScheduledThreadPool SingleThreadExecutor 前言 由于内容过多,所以将分为上下两部分,第一部分主要和大家谈谈Android中的线程,以及在Android中的常用的线程池.第二部分我们一起来了解一下AsyncTask的使用和工作原理. HandlerThread

随机推荐