Android dispatchTouchEvent touch事件的分发
2015-09-27 22:42
731 查看
一、简介
主要有三个函数:dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent
1、dispatchTouchEvent是用来分发touch事件的。touch事件首先到达这里。
2、onInterceptTouchEvent是用来拦截touch事件的,如果返回true则拦截事件,反之不拦截。只有在ViewGroup中有,View没有这个函数。如果拦截掉,就不会传送到子View中去。
3、onTouchEvent就是用来处理touch事件的。
touch事件基本的分发流程是这样的:
1、ViewGroup的dispatchTouchEvent接收到touch事件,调用onInterceptTouchEvent查看是否拦截。
2、如果拦截事件,则不传递事件到子View。在ViewGroup本身的onTouchEvent中处理。
3、如果没有拦截事件,则传递到子View的dispatchTouchEvent.
4、子View如果是ViewGroup,则回到1.
5、子View如果是View, 则查看是否有注册OnTouchListener
6、View如果没有注册onTouchListener或者OnTouchListener返回false,则执行onTouchEvent
Android很多事件都是类似这样分发的,有一个分发的函数和一个处理的函数组成。
二、细节问题
上面的描述阐述了事件分发的流程,但是还有一些细节问题需要解决
1、onClick, onLongClick, onTouchListener和View的onTouchEvent的关系是怎样的?他们的返回值会相互影响吗?
2、ViewGroup每个事件的分发,都要调用onInterceptTouchEvent吗?onInterceptTouchEvent返回值是否会对后续的分发有影响?
3、dispatchTouchEvent返回值有用吗?是否会影响后续事件的分发?
三、解决问题
1、第一个问题
这些事件主要在View里面处理的。先来看以下View的dispatchTouchEvent的源码:
函数说明:用来分发touch时间到目标View. 自己也可以是目标View. 如果返回值为true,表示事件已经被此View处理。反之,表示没处理。
重点看26行: li.mOnTouchListener.onTouch(this, event) 和30行: if (!result && onTouchEvent(event))
就是说,如果有注册onTouchListener的话,就执行onTouchListener. 如果onTouchListener返回true,则result值为true,就不再执行View的onTouchEvent。
所以,onTouchListener先于onTouchEvent执行,如果返回true的话,onTouchEvent就不再执行。
再来分析onClick和onLongClick事件。
看onTouchEvent的源码片段:
在ACTION_DOWN的时候,执行了 setPressed(true, x, y),设置按下的状态。
然后执行checkForLongClick(0),这个函数会post delay一个runnable去执行长按的事件。runnable代码如下:
所以,如果performLongClick返回true,则mHasPerformedLongPress 为true
然后看onTouchEvent的ACTION_UP的代码片段:
如果mHasPerformedLongPress为true的话,即onLongClick返回true,这时候,就不在执行onClick事件。反之,如果onLongClick返回false的话,则还会执行onClick事件。
所以说,如果按下的时间短的话,长按事件会被取消,会执行onClick事件。
如果按下时间达到长按的延迟时间,则会执行onLongClick事件。这时候,如果onLongClick返回true,则不执行onClick事件,反之,会再执行onClick事件。
问题小结:
View 的dispatchTouchEvent会先调用onTouchListener, 如果onTouchListener返回true的话,onTouchEvent的将不会执行。注意,onTouchListener是一定会执行的。
onClick和onLongClick也不会执行,因为他们是在onTouchEvent里面执行的。
如果同时注册onLongClickListener和onClickListener,如果onLongClick返回true的话,onClick不会再执行。反之,onClick会再执行。注意,onLongClick是一定会执行的。
2、第二个问题:
先看dispatchTouchEvent的源码片段:
可以看到,onInterceptTouchEvent只有在ACTION_DOWN的时候调用。如果onInterceptTouchEvent返回true,则mFirstTouchTarget 就没有机会赋值。所以intercepted 的值就一直为true,mFirstTouchTarget 一直为空。
再来看dispatchTouchEvent的源码片段:
dispathTransformedTouchEvent的声明如下:
当child为空的时候,就会执行到handled = super.dispatchTouchEvent。
super就是View,这时候就回到了第一个问题。
问题小结:
只有在ACTION_DOWN的时候,会调用onInterceptTouchEvent
在ACTION_DOWN的时候,如果onInterceptTouchEvent返回true的话,后续的事件都不会传给子View, ViewGruop会自己处理掉。
3,、第三个问题
从ViewGruop的dispatchTouchEvent函数源码片段看起:
第18行: final ArrayList<View> preorderedList = buildOrderedChildList();,先把子View从外到里排序。
第27行:if (!canViewReceivePointerEvents(child),,判断该子View能否点击,如果不能点击,则跳过
第28行:|| !isTransformedTouchPointInView(x, y, child, null))判断点击的点是否在该子View内,如果不在,则跳过
第41行:if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),这时候参数child不为空,所以,在dispatchTransformedTouchEvent函数里面会执行到child的dispatchTouchEvent。好,要点来了。如果child的dispatchTouchEvent在ACTION_DOWN的时候,返回false,则41行后面的语句不会执行,就不会加入到TouchTarget里面,即后续的事件不会再传送给它了。
第57行: newTouchTarget = addTouchTarget(child, idBitsToAssign);即把child加入到TouchTarget列表里面(mFirstTouchTarget记录TouchTarget列表),后续的事件都往这个列表里面发送了。依照从里往外的顺序。
所以,在子View的dispatchTouchEvent的ACTION_DOWN返回false之后,后续的事件就不传给它了。
接下来看以下dispatchTouchEvent的传送顺序:
刚才说过,在上面代码的第18行,把View从外到里的顺序排序了。第57行,addTouchTarget把child加入到mFirstTouchTarget的时候,就是按照从外到里的这个顺便。看一下addTouchTarget的源码:
从第7行和第8行,我们可以看出,每加入一个新的target的时候,就把mFirstTouchgetf赋值给target的next字段中,再把target赋值给mFirstTouchget。如此一来,先加入的target就跑到后面(next)去了。
在来看dispatchTouchEvent的源码片段:
看第9行,和ACTION_DOWN一样,执行了dispatchTransformedTouchEvent,最终同样会执行到child的dispatchTouchEvent。而child的dispatchTouchEvent如何处理,就是一个递归的问题了。其实这里应该是用到的组合模式。
问题小结:如何dispatchTouchEvent在ACTION_DOWN的时候,返回false,则后续的事件不会再传给它。如果返回true,获取的事件都会传给它。dispatchToucheEvent的传递顺序是从里到外的。
主要有三个函数:dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent
1、dispatchTouchEvent是用来分发touch事件的。touch事件首先到达这里。
2、onInterceptTouchEvent是用来拦截touch事件的,如果返回true则拦截事件,反之不拦截。只有在ViewGroup中有,View没有这个函数。如果拦截掉,就不会传送到子View中去。
3、onTouchEvent就是用来处理touch事件的。
touch事件基本的分发流程是这样的:
1、ViewGroup的dispatchTouchEvent接收到touch事件,调用onInterceptTouchEvent查看是否拦截。
2、如果拦截事件,则不传递事件到子View。在ViewGroup本身的onTouchEvent中处理。
3、如果没有拦截事件,则传递到子View的dispatchTouchEvent.
4、子View如果是ViewGroup,则回到1.
5、子View如果是View, 则查看是否有注册OnTouchListener
6、View如果没有注册onTouchListener或者OnTouchListener返回false,则执行onTouchEvent
Android很多事件都是类似这样分发的,有一个分发的函数和一个处理的函数组成。
二、细节问题
上面的描述阐述了事件分发的流程,但是还有一些细节问题需要解决
1、onClick, onLongClick, onTouchListener和View的onTouchEvent的关系是怎样的?他们的返回值会相互影响吗?
2、ViewGroup每个事件的分发,都要调用onInterceptTouchEvent吗?onInterceptTouchEvent返回值是否会对后续的分发有影响?
3、dispatchTouchEvent返回值有用吗?是否会影响后续事件的分发?
三、解决问题
1、第一个问题
这些事件主要在View里面处理的。先来看以下View的dispatchTouchEvent的源码:
/** * 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) { 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; }
函数说明:用来分发touch时间到目标View. 自己也可以是目标View. 如果返回值为true,表示事件已经被此View处理。反之,表示没处理。
重点看26行: li.mOnTouchListener.onTouch(this, event) 和30行: if (!result && onTouchEvent(event))
就是说,如果有注册onTouchListener的话,就执行onTouchListener. 如果onTouchListener返回true,则result值为true,就不再执行View的onTouchEvent。
所以,onTouchListener先于onTouchEvent执行,如果返回true的话,onTouchEvent就不再执行。
再来分析onClick和onLongClick事件。
看onTouchEvent的源码片段:
case MotionEvent.ACTION_DOWN: .... if (isInScrollingContainer) { .... } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break;
在ACTION_DOWN的时候,执行了 setPressed(true, x, y),设置按下的状态。
然后执行checkForLongClick(0),这个函数会post delay一个runnable去执行长按的事件。runnable代码如下:
private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; @Override public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; } } } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } }
所以,如果performLongClick返回true,则mHasPerformedLongPress 为true
然后看onTouchEvent的ACTION_UP的代码片段:
case MotionEvent.ACTION_UP: .... 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(); } } }
如果mHasPerformedLongPress为true的话,即onLongClick返回true,这时候,就不在执行onClick事件。反之,如果onLongClick返回false的话,则还会执行onClick事件。
所以说,如果按下的时间短的话,长按事件会被取消,会执行onClick事件。
如果按下时间达到长按的延迟时间,则会执行onLongClick事件。这时候,如果onLongClick返回true,则不执行onClick事件,反之,会再执行onClick事件。
问题小结:
View 的dispatchTouchEvent会先调用onTouchListener, 如果onTouchListener返回true的话,onTouchEvent的将不会执行。注意,onTouchListener是一定会执行的。
onClick和onLongClick也不会执行,因为他们是在onTouchEvent里面执行的。
如果同时注册onLongClickListener和onClickListener,如果onLongClick返回true的话,onClick不会再执行。反之,onClick会再执行。注意,onLongClick是一定会执行的。
2、第二个问题:
先看dispatchTouchEvent的源码片段:
// Check for interception. final boolean intercepted; 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 { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }
可以看到,onInterceptTouchEvent只有在ACTION_DOWN的时候调用。如果onInterceptTouchEvent返回true,则mFirstTouchTarget 就没有机会赋值。所以intercepted 的值就一直为true,mFirstTouchTarget 一直为空。
再来看dispatchTouchEvent的源码片段:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
dispathTransformedTouchEvent的声明如下:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)
当child为空的时候,就会执行到handled = super.dispatchTouchEvent。
super就是View,这时候就回到了第一个问题。
问题小结:
只有在ACTION_DOWN的时候,会调用onInterceptTouchEvent
在ACTION_DOWN的时候,如果onInterceptTouchEvent返回true的话,后续的事件都不会传给子View, ViewGruop会自己处理掉。
3,、第三个问题
从ViewGruop的dispatchTouchEvent函数源码片段看起:
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 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. 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); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } 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; } resetCancelNextUpFlag(child); 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; } } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } }看第1行,这是在ACTION_DOWN里面判断的。记住,是在ACTION_DOWN里面判断的。
第18行: final ArrayList<View> preorderedList = buildOrderedChildList();,先把子View从外到里排序。
第27行:if (!canViewReceivePointerEvents(child),,判断该子View能否点击,如果不能点击,则跳过
第28行:|| !isTransformedTouchPointInView(x, y, child, null))判断点击的点是否在该子View内,如果不在,则跳过
第41行:if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),这时候参数child不为空,所以,在dispatchTransformedTouchEvent函数里面会执行到child的dispatchTouchEvent。好,要点来了。如果child的dispatchTouchEvent在ACTION_DOWN的时候,返回false,则41行后面的语句不会执行,就不会加入到TouchTarget里面,即后续的事件不会再传送给它了。
第57行: newTouchTarget = addTouchTarget(child, idBitsToAssign);即把child加入到TouchTarget列表里面(mFirstTouchTarget记录TouchTarget列表),后续的事件都往这个列表里面发送了。依照从里往外的顺序。
所以,在子View的dispatchTouchEvent的ACTION_DOWN返回false之后,后续的事件就不传给它了。
接下来看以下dispatchTouchEvent的传送顺序:
刚才说过,在上面代码的第18行,把View从外到里的顺序排序了。第57行,addTouchTarget把child加入到mFirstTouchTarget的时候,就是按照从外到里的这个顺便。看一下addTouchTarget的源码:
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */ private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
从第7行和第8行,我们可以看出,每加入一个新的target的时候,就把mFirstTouchgetf赋值给target的next字段中,再把target赋值给mFirstTouchget。如此一来,先加入的target就跑到后面(next)去了。
在来看dispatchTouchEvent的源码片段:
TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; }
看第9行,和ACTION_DOWN一样,执行了dispatchTransformedTouchEvent,最终同样会执行到child的dispatchTouchEvent。而child的dispatchTouchEvent如何处理,就是一个递归的问题了。其实这里应该是用到的组合模式。
问题小结:如何dispatchTouchEvent在ACTION_DOWN的时候,返回false,则后续的事件不会再传给它。如果返回true,获取的事件都会传给它。dispatchToucheEvent的传递顺序是从里到外的。
相关文章推荐
- idea or android studio 使用
- 百度云分享,以前找到的各种东东,分享给大家···Office软件(全-C)
- Android 中的 Service 全面总结(转载)
- Android学习入门
- Android入门——远程Remote Service AIDL详解及应用
- android中fragment实现底部标签页的切换
- android开发笔记——android工程目录结构:
- Android——Layout:RelativeLayout
- Android Device Monitor的 “provide the path to the Android SDK”问题解决方案
- android应用开发全程实录-你有多熟悉listview?
- Android的Message机制(简单小结)
- Android 线程 thread 两种实现方法!
- Android——Layout:LinearLayout
- Android——Layout:LinearLayout
- Action Bar
- FEDORA UT4418开发板ANDROID环境搭建
- Android中的Service相关全面总结
- Animation——动画
- 在Activity中注册广播
- Android Studio开发第2篇版本管理Git