Android 事件分发机制-源码分析
来源:互联网 发布:打折邮票淘宝推荐店铺 编辑:程序博客网 时间:2024/06/05 14:22
说明
具体流程如图所示,
对于
dispatchTouchEvent
,onTouchEvent
返回 true 就是自己消费了,返回 false 就传到父View 的onTouchEvent
方法ViewGroup 想把事件分发给自己的
onTouchEvent
,需要在onInterceptTouchEvent
方法中返回 true 把事件拦截下来ViewGroup 的
onInterceptTouchEvent
默认不拦截,所以super.onInterceptTouchEvent() = false
View(这里指没有子View)没有拦截器,所以 View 的
dispatchTouchEvent
的super.dispatchTouchEvent(event)
默认把事件分发给自己的onTouchEvent
源码解析(以下内容部分来自书籍《Android开发艺术探索》)
Activity对点击事件的分发过程
当一个点击事件发生时,事件首先传递给当前 Activity ,由 Activity 的
dispatchTouchEvent()
来进行事件的分发,具体的工作是由 Activity 内部 Window来完成。Window 会将事件传递给 decor view, decor view 一般就是当前 Activity 的顶层 View, 源码如下:Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
- 事件交给 Activity 所附属的 Window 进行分发, 如果返回 true 整个循环就结束了,返回 false 就表示没有人要处理,交由 Activity 的
onTouchEvent
处理;可知 Activity 调用getWindow().superDispatchTouchEvent(ev)
把事件分发给ViewGroup, 所以我们来看
Windows#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event)
- 可以看出 Window 是个抽象类,且 Window 唯一实现的是 PhoneWindow,所以接下来我们看看 PhoneWindow 怎么处理该方法的
PhoneWindow#superDispatchTouchEvent
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
- 到这里清晰, 是在 PhoneWidow 将事件传递给了 DecorView, 至于什么是 DercorView ,看源码里如何解释的:
// This is the top-level view of the window, containing the window decor. private DecorView mDecor;
这个 mDecor 显然就是getWindow().getDecorView()
所返回的 View,而我们通过 setContentView
设置的 View 就是是它的一个子 view。目前事件传递到了 DecorView 这里,由于 DecorView 继承自 FrameLayout 且又是父 View,所以最终事件会传递给 我们所设置setContentView
的顶级 View 一般来说都是 ViewGroup(不传递给他怎么响应用户点击事件呢��)
ViewGroup 对点击事件的分发
- ViewGroup 对点击事件的分发过程主要实现在 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; }
可以看出 ViewGroup 在两种情况下会判断是否要拦截当前事件:
- 事件类型 为
ACTION_DOWN
,这个很好理解 - 或者
mFirstTouchTarget != null
, 这个从后面代码可以看出,当事件由 ViewGroup 的子元素成功处理时,mFirstTouchTarget
就会被赋值并指向子元素
- 事件类型 为
且当
ACTION_MOVE
和ACTION_UP
事件到来时,由于actionMasked == MotionEvent.ACTION_DOWN
这个条件为 false ,将导致 ViewGroup 的
|| mFirstTouchTarget != nullonInterceptTouchEvent
不被调用,并且同一序列中的其他事件都会默认交给他处理。但是这里有个特殊情况,那就是
FLAG_DISALLOW_INTERCEPT
标记,这个标记是子 View 通过requestDisallowInterceptTouchEvent
方法来设置。一旦设置了该标记,ViewGroup 将无法拦截除了ACTION_DOWN
以外的事件。为什么说除了ACTION_DOWN
以外的事件,这点从源码也可以看出:因为在 ViewGroup 在事件分发时,如果是ACTION_DOWN
就会重置FLAG_DISALLOW_INTERCEPT
标记,将导致子View 设置的这个标记失效。因此 当事件为ACTION_DOWN
时 ViewGroup 总是会调用自己的onInterceptTouchEvent
来询问是否拦截事件。
我们看看上面代码的前一句代码就明白了:
代码位置// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }
- 从上面代码可以看出, ViewGroup 在
ACTION_DOWN
事件时会做重置操作:会在resetTouchState()
对FLAG_DISALLOW_INTERCEPT
标记进行重置,因此子 View调用requestDisallowInterceptTouchEvent
方法并不能影响 ViewGroup 对ACTION_DOWN
事件的处理
- 从上面代码可以看出, ViewGroup 在
从上面分析我们可以总结出两点:
onInterceptTouchEvent
不是每次事件都会被调用,如果我们想在当前的 ViewGroup 处理所有的点击事件,就要选择onInterceptTouchEvent
方法中处理,只有这个方法能确保每次都被调用FLAG_DISALLOW_INTERCEPT
给我们提供了另一种思路去解决滑动冲突的方法:在子 View 拦截处理
接下来我们看 ViewGroup 怎么把事件传递给子 View的:
代码位置
final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); 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; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false);}
如上面源码所示,首先操作的是遍历 ViewGroup 的所有子元素,然后判断是否能够接收到点击事件。是否能够接收点击事件主要有两点判断:
- 子元素是否在播放动画
点击事件的坐标是否在子元素坐标区域内
可以看到他调用了dispatchTransformedTouchEvent
方法来传递事件,所以我们来看看该方法
ViewGroup#dispatchTransformedTouchEventprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }........}
可以看到在该方法里如果 传递的 child 不是 null 他会直接调用的是子元素的
dispatchTouchEvent
方法来把事件传递给子元素
再回到 遍历 ViewGroup 的所有子元素的方法中,可以看到在循环的最后过程中,判断如果子元素的
dispatchTouchEvent
返回 true ,那么这个 ViewGroup 就暂时不考虑事件在子元素内部是怎么分发的,而且mFirstTouchTarget
就会被赋值同时跳出 for 循环,如下所示:
代码位置newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;
在这里完成了
mFirstTouchTarget
的赋值并且终止了对子元素的遍历。其实对mFirstTouchTarget
的赋值是在addTouchTarget
方法里完成的:
ViewGroup#addTouchTargetprivate TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target;}
mFirstTouchTarget
其实是一种单链表结构,他是否被赋值将直接影响到 ViewGroup 对事件的拦截策略,若果mFirstTouchTarget
为 null ,那么 ViewGroup 就默认拦截接下来同一序列中所有的点击事件
如果遍历所有的子元素后事件都没有被合适处理,这里包含两种情况:
- ViewGroup 没有子元素
- 子元素处理了点击事件,但是在
dispatchTouchEvent
中返回了 false (这一般是因为子元素在onTouch
中返回了false)
在这两种情况下 ViewGroup 会自己处理点击事件:// 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);}
View对点击事件的分发过程
View 对点击事件的处理稍微简单点,首先看他的
dispatchTouchEvent
方法
View#dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent event) {......if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //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;}}
- 由于 View 不包含子元素,所以他无法传递事件只能自己处理。从上面的源码可以看出,View 对事件的处理首先会判断有没有设置
OnTouchListener
如果设置了且OnTouchListener
中的onTouch
放回 true,那么 View 的onTouchEvent
就不会被调用。由此可见OnTouchListener
的优先级高于onTouchEvent
- 由于 View 不包含子元素,所以他无法传递事件只能自己处理。从上面的源码可以看出,View 对事件的处理首先会判断有没有设置
接着我们再看
onTouchEvent
的实现。先看当 View 处于不可用状态下的点击事件处理过程:
View#onTouchEventif ((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));}
- 很明显:不可用状态下的 View 照样会消耗点击事件,尽管它看起来不可用
接着
onTouchEvent
,如果 View 设置有代理,那么还会执行TouchDelegate
的onTouchEvent
方法if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) { return true;}}
再看
onTouchEvent
中对点击事件的具体处理: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(); } } } ..... } break; } .... return true;}
可以看出只要 View 的
CLICKABLE
和LONG_CLICKABLE
有一个为 true,那么他就会消耗这个事件,即onTouchEvent
返回 true,不管他是不是DISABLE
状态。然后在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;}
若果 View 设置了
OnClickListener
,那么performClick()
方法内部会调用它的onClick
方法
End. 到此就结束了
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- 源码分析android的事件分发机制
- Android 事件分发机制-源码分析
- 事件分发机制源码分析
- Android事件分发机制源码分析上----View事件分发分析
- Android事件分发机制源码分析下----ViewGroup事件分发分析
- android 从源码分析view事件分发机制
- Android 从源码角度分析事件分发机制(三)
- 从源码角度分析android事件分发处理机制
- 从源码角度分析android事件分发处理机制
- Android View和ViewGroup事件分发机制源码分析
- Android 事件分发机制(最新源码6.0分析)--childView
- Android 事件分发机制(最新源码6.0分析)--ViewGrop
- Android—— View事件分发机制的源码分析
- Android事件分发机制源码分析之View篇
- 解决dyld: Library not loaded: @rpath/libswiftCore.dylib
- rails分页(kaminari)
- 浅谈AndroidStudio2.3.3添加讯飞语音功能
- 应用统计学与R语言实现学习笔记后记
- gdi+图像去污
- Android 事件分发机制-源码分析
- jQuery实现清空table表格除首行外的所有数据
- Could not autowire. No beans of 'xxxx' type found
- <划重点的Unity2017>Playable相关
- HDFS客户端的权限错误:Permission denied
- npm is known not to run on Node.js vue启动报错
- Linux学习-1
- 计算空间点到平面的投影点坐标(代码)
- 利用xshell上传文件到linux