6. Android 校招复习篇二・事件分发
事件分发
工作原理
事实上,View 事件分发的本质是递归。
递流程的方向遵循以下流程:
归流程的方向遵循以下流程:
每次完整的事件分发流程,都包含自上而下的“递”,与自下而上的“归”2个流程。
每次完整的事件分发流程,都是针对一个事件(MotionEvent)完成的递归,而一个事件只对应着一个
Action
,例如:ACTION_DOWN
。一次用户触摸操作,我们称之为一个事件序列。一个事件序列会包含
ACTION_DOWN
、ACTION_MOVE
…ACTION_MOVE
、ACTION_UP
等多个事件。也即一个事件序列,包含从
ACTION_DOWN
到ACTION_UP
的多次事件分发流程。
3个重要方法
事先分发包含 3 个重要方法: dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
大致流程
递流程
dispatchTouchEvent(ev)
在“递”的过程中,主要通过dispatchTouchEvent
方法进行事件的向下分发。
因此“递”流程可以改进为:
该方法主要执行以下操作(分两种情况):
如果 child 是 ViewGroup,那么实际执行的就是 ViewGroup 重写的 dispatchTouchEvent 方法。该方法内可以判断,是否在当前层级拦截当前事件、或者继续把事件下发给下一级。
如果 child 是不再有 child 的 View 或 ViewGroup,那么实际执行的就是 View 类实现的 super.dispatchTouchEvent 方法。该方法内可以判断,如果
View.enabled == true
且实现了 onTouchListener
且onTouch() 返回 true
,那么不执行 onTouchEvent( ),并直接返回 true 表示事件已被消费,然后步入“归”流程;否则执行 onTouchEvent( )。流程如下:
View 事件分发流程-View#dispatchTouchEvent 相关源码(仅贴出关键代码):
1 2 3 4 5 6 7
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
从流程图或源码可以看出:
- 若手动复写在
onTouch()
中返回true
(即将事件消费掉,将不会再执行onTouchEvent()
- 若一个控件不可点击(即
View.enabled == false
),那么给它注册的onTouch()
事件将永远得不到执行。因为这是逻辑与的判断,而判断 View 是否可点击在执行onTouch()
事件之前。
- 若手动复写在
onTouchEvent(ev)
onTouchEvent 方法的流程如下:
分析:在onTouchEvent()
中,如果View.clickabled == true
并且实现了onClickListener
或onLongClickListener
,就会执行onClick()
或onLongClick()
。
onInterceptTouchEvent(ev)
事实上,在“递”的流程中,ViewGroup 可以在当前层级通过设置onInterceptTouchEvent()
方法返回true
,来拦截事件的下发,然后直接步入“归”流程。
注意:
该拦截方法只存在于 ViewGroup,普通的 View 无该方法
如果某一层级的 ViewGroup 拦截了某个事件,那么后续的这一事件序列都会默认拦截,不再调用此方法。
即
onInterceptTouchEvent()
方法只走一次,一旦走过,就会留下记号(mFirstTouchTarget == null)
,那么下一次直接根据这个记号来判断拦不拦截。
相关源码(仅贴出关键代码):
|
|
那么有同学就好奇了,disallowIntercept
又是什么东西啊?
正所谓“上有政策,下有对策”。在ViewGroup
可以拦截事件下发的同时,child
也可以通过getParent.requestDisallowInterceptTouchEvent()
方法来改变disallowIntercept
的值,从而阻止上一级的下发拦截(即关闭拦截功能,使得原本被拦截的事件继续下发)。
说明:disallowIntercept = 是否禁用事件拦截功能(默认值 false)
归流程
总之,递流程走到没有 child 的层级,就意味着步入“归”流程。
如果该层级的super.dispatchTouchEvent(ev)
没有返回true
,那么将继续执行上一级的super.dispatchTouchEvent(ev)
,直到被某一级消费为止(也即返回true
为止)。
这就是“归”流程。
总结
综上,整个大致流程如下图:
应用场景
滑动冲突解决
冲突场景
要点
外部拦截法
重写onInterceptTouchEvent()
,根据冲突场景的规则来判断是否拦截。
内部拦截法
重写子 View 的dispatchTouchEvent()
,然后调用parent.requestDisallowInterceptTouchEvent(true)
禁止父容器拦截事件,全部交给子 View 处理。
额外知识
onTouch()和onTouchEvent()的区别?
- 2个方法都是在
View.dispatchTouchEvent()
中调用,但onTouch()
优先于onTouchEvent()
执行 - 若手动复写在
onTouch()
中返回true
(即将事件消费掉,将不会再执行onTouchEvent()