深入解析Andoird应用开发中View的事件传递

 更新时间:2016年02月18日 17:45:49   作者:iam_wingjay  
这篇文章主要介绍了深入解析Andoird应用开发中View的事件传递,其中重点讲解了ViewGroup的事件传递流程,需要的朋友可以参考下

2016218174438007.jpg (602×339)

下面以点击某个view之后的事件传递为例子。
首先分析view里的dispatchTouchEvent()方法,它是点击view执行的第一个方法。

public boolean dispatchTouchEvent(MotionEvent event) {
 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
   mOnTouchListener.onTouch(this, event)) {
   return true;
 }
 return onTouchEvent(event);
}

注意:里面包含两个回调函数 onTouch(),onTouchEvent();如果控件绑定了OnTouchListener,且该控件是enabled,那么就执行onTouch()方法,如果该方法返回true,则说明该触摸事件已经被OnTouchListener监听器消费掉了,不会再往下分发了;但是如果返回false,则说明未被消费,继续往下分发到该控件的onTouchEvent()去处理。

然后分析onTouchEvent()方法,进行进一步的触摸事件处理。

if (((viewFlags & CLICKABLE) == CLICKABLE || 
   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 
  switch (event.getAction()) { 
   case MotionEvent.ACTION_UP:
    ..... 
      performClick(); //响应点击事件 
    break; 
   case MotionEvent.ACTION_DOWN: 
   ..... break; 
   case MotionEvent.ACTION_CANCEL: 
   ..... break; 
   case MotionEvent.ACTION_MOVE: 
   ..... break; 
  } 
  return true; 
} 
return false;

如果该控件是clickable 、long_clickable的,那么就可以响应对应事件,响应完后返回true继续响应。比如点击事件,先响应ACTION_DOWN,然后break并返回true,然后手抬起,又从dispatchTouchEvent()分发下来,再响应ACTION_UP,里面会去performClick()响应点击事件。

响应点击事件

public boolean performClick() {
  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  if (mOnClickListener != null) {
     playSoundEffect(SoundEffectConstants.CLICK);
     mOnClickListener.onClick(this);
     return true;
  }
  return false;
}

里面执行mOnClickListener.onClick(this);即回调绑定监听器的onClick()函数。

关键点:
onTouch和onTouchEvent的区别,又该如何使用?
答:
当view控件接受到触摸事件,如果控件绑定了onTouchListener监听器,而且该控件是enable,那么就去执行onTouch()方法,如果返回true,则已经把触摸事件消费掉,不再向下传递;如果返回false,那么继续调用onTouchEvent()事件。

Android的Touch事件传递到Activity顶层的DecorView(一个FrameLayout)之后,会通过ViewGroup一层层往视图树的上面传递,最终将事件传递给实际接收的View。下面给出一些重要的方法。

dispatchTouchEvent
事件传递到一个ViewGroup上面时,会调用dispatchTouchEvent。代码有删减

public boolean dispatchTouchEvent(MotionEvent ev) {

  boolean handled = false;
  if (onFilterTouchEventForSecurity(ev)) {
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    // Attention 1 :在按下时候清除一些状态
    if (actionMasked == MotionEvent.ACTION_DOWN) {
      cancelAndClearTouchTargets(ev);
      //注意这个方法
      resetTouchState();
    }

    // Attention 2:检查是否需要拦截
    final boolean intercepted;
    //如果刚刚按下 或者 已经有子View来处理
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
      } else {
        intercepted = false;
      }
    } else {
      // 不是一个动作序列的开始 同时也没有子View来处理,直接拦截
      intercepted = true;
    }

     //事件没有取消 同时没有被当前ViewGroup拦截,去找是否有子View接盘
    if (!canceled && !intercepted) {
        //如果这是一系列动作的开始 或者有一个新的Pointer按下 我们需要去找能够处理这个Pointer的子View
      if (actionMasked == MotionEvent.ACTION_DOWN
          || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
          || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        final int actionIndex = ev.getActionIndex(); // always 0 for down

        //上面说的触碰点32的限制就是这里导致
        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
            : TouchTarget.ALL_POINTER_IDS;

        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
          final float x = ev.getX(actionIndex);
          final float y = ev.getY(actionIndex);

          //对当前ViewGroup的所有子View进行排序,在上层的放在开始
          final ArrayList<View> preorderedList = buildOrderedChildList();
          final boolean customOrder = preorderedList == null
              && isChildrenDrawingOrderEnabled();
          final View[] children = mChildren;
          for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = customOrder
                ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);

               // canViewReceivePointerEvents visible的View都可以接受事件
               // isTransformedTouchPointInView 计算是否落在点击区域上
            if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
              ev.setTargetAccessibilityFocus(false);
              continue;
            }

               //能够处理这个Pointer的View是否已经处理之前的Pointer,那么把
            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
              // Child is already receiving touch within its bounds.
              // Give it the new pointer in addition to the ones it is handling.
              newTouchTarget.pointerIdBits |= idBitsToAssign;
              break;
            }              }
            //Attention 3 : 直接发给子View
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              // Child wants to receive touch within its bounds.
              mLastTouchDownTime = ev.getDownTime();
              if (preorderedList != null) {
                // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                  if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                  }
                }
              } else {
                mLastTouchDownIndex = childIndex;
              }
              mLastTouchDownX = ev.getX();
              mLastTouchDownY = ev.getY();
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
            }

          }
        }

      }
    }

    // 前面已经找到了接收事件的子View,如果为NULL,表示没有子View来接手,当前ViewGroup需要来处理
    if (mFirstTouchTarget == null) {
      // ViewGroup处理
      handled = dispatchTransformedTouchEvent(ev, canceled, null,
          TouchTarget.ALL_POINTER_IDS);
    } else {

        if(alreadyDispatchedToNewTouchTarget) {
                   //ignore some code
          if (dispatchTransformedTouchEvent(ev, cancelChild,
              target.child, target.pointerIdBits)) {
            handled = true;
         }
        }

    }
  return handled;
}

上面代码中的Attention在后面部分将会涉及,重点注意。

这里需要指出一点的是,一系列动作中的不同Pointer可以分配给不同的View去响应。ViewGroup会维护一个PointerId和处理View的列表TouchTarget,一个TouchTarget代表一个可以处理Pointer的子View,当然一个View可以处理多个Pointer,比如两根手指都在某一个子View区域。TouchTarget内部使用一个int来存储它能处理的PointerId,一个int32位,这也就是上层为啥最多只能允许同时最多32点触碰。

看一下Attention 3 处的代码,我们经常说view的dispatchTouchEvent如果返回false,那么它就不能系列动作后面的动作,这是为啥呢?因为Attention 3处如果返回false,那么它不会被记录到TouchTarget中,ViewGroup认为你没有能力处理这个事件。

这里可以看到,ViewGroup真正处理事件是在dispatchTransformedTouchEvent里面,跟进去看看:

dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
    View child, int desiredPointerIdBits) {

   //没有子类处理,那么交给viewgroup处理
  if (child == null) {
    handled = super.dispatchTouchEvent(transformedEvent);
  } else {
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    transformedEvent.offsetLocation(offsetX, offsetY);
    if (! child.hasIdentityMatrix()) {
      transformedEvent.transform(child.getInverseMatrix());
    }

    handled = child.dispatchTouchEvent(transformedEvent);
  }
  return handled;
}

可以看到这里不管怎么样,都会调用View的dispatchTouchEvent,这是真正处理这一次点击事件的地方。

dispatchTouchEvent
  public boolean dispatchTouchEvent(MotionEvent event) {
    if (onFilterTouchEventForSecurity(event)) {
    //先走View的onTouch事件,如果onTouch返回True
    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;
    }
  }
    return result;
  }

我们给View设置的onTouch事件处在一个较高的优先级,如果onTouch执行返回true,那么就不会去走view的onTouchEvent,而我们一些点击事件都是在onTouchEvent中处理的,这也是为什么onTouch中返回true,view的点击相关事件不会被处理。

小小总结一下这个流程
ViewGroup在接受到上级传下来的事件时,如果是一系列Touch事件的开始(ACTION_DOWN),ViewGroup会先看看自己需不需要拦截这个事件(onInterceptTouchEvent,ViewGroup的默认实现直接返回false表示不拦截),接着ViewGroup遍历自己所有的View。找到当前点击的那个View,马上调用目标View的dispatchTouchEvent。如果目标View的dispatchTouchEvent返回false,那么认为目标View只是在那个位置而已,它并不想接受这个事件,只想安安静静的做一个View(我静静地看着你们装*)。此时,ViewGroup还会去走一下自己dispatchTouchEvent,Done!

相关文章

  • Java常用集合与原理解析

    Java常用集合与原理解析

    这篇文章主要介绍了Java常用集合与原理解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • Springboot 异步任务和定时任务的异步处理

    Springboot 异步任务和定时任务的异步处理

    本文介绍了Springboot异步任务和定时任务的异步处理,Springboot 中,异步任务和定时任务是经常遇到的处理问题方式,为了能够用好这两项配置,不干扰正常的业务,需要对其进行异步化配置。怎么设置合理的异步处理线程就是其核心和关键,下文详情需要的朋友可以参考下
    2022-05-05
  • 利用Java连接Hadoop进行编程

    利用Java连接Hadoop进行编程

    这篇文章主要介绍了利用Java连接Hadoop进行编程,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-06-06
  • SpringBoot实现mysql与clickhouse多数据源的项目实践

    SpringBoot实现mysql与clickhouse多数据源的项目实践

    本文主要介绍了SpringBoot实现mysql与clickhouse多数据源的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11
  • Java中的CyclicBarrier循环栅栏解析

    Java中的CyclicBarrier循环栅栏解析

    这篇文章主要介绍了Java中的CyclicBarrier循环栅栏解析,从字面上的意思可以知道,这个类的中文意思是"循环栅栏",大概的意思就是一个可循环利用的屏障,它的作用就是会让所有线程都等待完成后才会继续下一步行动,需要的朋友可以参考下
    2023-12-12
  • Springboot基于BCrypt非对称加密字符串的实现

    Springboot基于BCrypt非对称加密字符串的实现

    本文主要介绍了Springboot基于BCrypt非对称加密字符串的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Java按行读取文件文本内容的方式汇总

    Java按行读取文件文本内容的方式汇总

    在工作和学习中,有时候会有一些场景,代码需要配合读取文件来执行,比如:读文件数据,来进行计算、组装SQL、更新操作,本文给大家介绍在Java中按行读取文件文件内容的方式有哪些,感兴趣的朋友一起看看吧
    2023-10-10
  • SWT(JFace)体验之RowLayout布局

    SWT(JFace)体验之RowLayout布局

    相对于FillLayout来说,RowLayout比较灵活,功能也比较强。用户可以设置布局中子元素的大小、边距、换行及间距等属性。
    2009-06-06
  • Java使用协同过滤算法的代码示例

    Java使用协同过滤算法的代码示例

    在Java中实现协同过滤算法通常需要一些步骤,包括加载用户-项目评分数据、计算相似度、生成推荐等,以下是一个简化的基于用户的协同过滤算法的代码示例,感兴趣的小伙伴跟着小编一起来看看吧
    2024-06-06
  • 通过spring注解开发,简单测试单例和多例区别

    通过spring注解开发,简单测试单例和多例区别

    这篇文章主要介绍了通过spring注解开发,简单测试单例和多例区别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08

最新评论