下面以点击某个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!
本文向大家介绍深入解析Android App开发中Context的用法,包括了深入解析Android App开发中Context的用法的使用技巧和注意事项,需要的朋友参考一下 Context在开发Android应用的过程中扮演着非常重要的角色,比如启动一个Activity需要使用context.startActivity方法,将一个xml文件转换为一个View对象也需要使用Context对象,可以
本文向大家介绍深入浅析vue组件间事件传递,包括了深入浅析vue组件间事件传递的使用技巧和注意事项,需要的朋友参考一下 由于新工作需要用vue,所以最近接触最多的也是vue,因为之前一直在用react,所以对于vue上手还是很快的。 我也尽量找一些他们两个的异同点,除了多了一些辅助用的方法以外,最大的不同应该是对于组件间的通信,不仅有props,还有一种事件监听,也是可以通过组件间传递的。 但是,
本文向大家介绍深入解析C++程序中激发事件和COM中的事件处理,包括了深入解析C++程序中激发事件和COM中的事件处理的使用技巧和注意事项,需要的朋友参考一下 本机 C++ 中的事件处理 在处理本机 C ++ 事件时,您分别使用 event_source 和 event_receiver 特性设置事件源和事件接收器,并指定 type=native。这些特性允许应用它们的类在本机的非 COM 上下文
本文向大家介绍深入解析Java中的JDBC事务,包括了深入解析Java中的JDBC事务的使用技巧和注意事项,需要的朋友参考一下 事务 事务是一步或多步组成操作序列组成的逻辑执行单元,这个序列要么全部执行,要么则全部放弃执行。事务的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(IsoIation)和持续性(Durability)原子性(Atomicity):事务应
本文向大家介绍深入浅析Vue组件开发,包括了深入浅析Vue组件开发的使用技巧和注意事项,需要的朋友参考一下 前言 这里讲的主要是想谈谈基于Vue的一个组件开发。不得不说的一点就是,在实际的Vue项目中,页面中每一个小块都是由一个个组件(.vue文件)组成,经过抽离后,然后再合并一起组成一个页面。由于上家公司我负责多的是可视化这一块的开发,这边我也将带着大家进行一个Vue项目中的可视化组件的开发,这
本文向大家介绍深入解析JavaScript框架Backbone.js中的事件机制,包括了深入解析JavaScript框架Backbone.js中的事件机制的使用技巧和注意事项,需要的朋友参考一下 事件模型及其原理 Backbone.Events就是事件实现的核心,它可以让对象拥有事件能力 对象通过listenTo侦听其他对象,通过trigger触发事件。可以脱离Backbone的MVC,在自定义的