View 自定义 - 事件分发

一、点击事件 MotionEvent

当点击屏幕的时候就会产生点击事件,包括:按下、滑动、抬起、取消,这些事件被封装成 MotionEvent 对象。一个事件流以按下为开始,中间可能有若干次滑动,以抬起或取消作为结束,即DOWN+不确定个MOVE+UP或CANCEL。系统会将一次完整的点击事件传递给View层级,这个过程就是点击事件分发。

MotionEvent.ACTION_DOWN

手指接触到屏幕产生。

MotionEvent.ACTION_MOVE

手指在屏幕上移动产生。

MotionEvent.ACTION_UP

手指离开屏幕产生。

MotionEvent.ACTION_CANCEL

滑动超出控件边界、父控件拦截了MOVE或UP、UP不是在同一个控件上触发,子控件就会收到。

二、分发过程

Android 的 UI 由Activity、ViewGroup、View及其派生类组成,事件也就在它们中进行传递。响应机制是先分发(由外向内传递)再处理(由内向外回溯),过程由dispatchTouchEvent(事件分发)、onTouchEvent(事件处理)、onInterceptTouchEvent(事件拦截) 协作完成。主要是对 Down 事件作分发,进而找到能够处理Down事件的组件,对于事件流中后续的事件(如Move、Up等),则直接分发给能够处理按下事件的组件。

2.1 外部传递到ViewGroup中

详见:Activity、Window、DecorView、ViewRoot 之间的关系

Activity会通过 getWindow() 获取 PhoneWindow 对象并调用它的 superDispatchTouchEvent(),该方法会调用它PhoneWindow 的内部类 DecorView 的 superDispatchTouchEvent(),而它 DecorView 继承自 FrameLayout,因此 DecorView 是一个顶级的 ViewGroup,这就实现了事件从外部到 ViewGroup 的传递。

2.2 ViewGroup中传递

2.2.1 ACTION_DOWN事件

返回值\方法

dispatchTouchEvent( )

事件分发

onTouchEvent( )

事件处理

true

一旦返回 true 事件就停止传递了,即该事件在此控件的该方法中被消费了。

false

返回false会将事件回传给父控件的onTouchEvent( )处理。

停止向下传递并往父控件回溯。

不消费事件继续向上传递。

super

ViewGroup:分发中会存在拦截的情况,会调用onInterceptTouchEvent( )来控制,返回super/false会继续向下传递给子控件(默认不拦截,因为子控件也需要该事件)。返回true会拦截事件给自己的onTouchEvent( )处理。

View:没有子控件因此没有事件拦截方法,默认实现就是来调用自己的onTouchEvent( )。

如果没有重写,默认情况下dispatchTouchEvent( )到onTouchEvent( )连起来整个流程呈一个U型。

2.2.2 ACTION_MOVE、ACTION_UP事件

DOWN在哪个控件中消费,MOVE 和 UP 通过 dispatchTouchEvent( ) 就只传递到该控件为止,不会继续往下传递。该控件中 DOWN 如果是在 dispatchTouchEvent( ) 中消费的,那么就到此为止,如果是在 onTouchEvent( ) 中消费的,那么会传递过去。如果都不消费 DOWN 事件则会传递给 Activity 的 onTouchEvent( ) 处理。

2.2.3 点击事件监听

  • 优先级:dispatchTouchEvent( ) > onTouchListener > onTouchEvent > onLongClickListener > onClickListener

  • 父控件和子控件同时设置了监听,优先响应子控件的。如果父控件先响应必定拦截了事件,不会向下传递子控件便永远无法响应。

三、自定义控件

  • 编写自定义 ViewGroup 的时候不要直接拦截所有事件,DOWN 一定要传递到最底层,否则拦得太死传递不下去,子控件没办法申请父控件不要拦截(子控件在 dispatchTouchEvent( ) 写入一行 getParent().resquestDisallowInterceptTouchEvent(true) )。

  • 当ViewGroup 要拦截事件时,拦截逻辑写在onInterceptTouchEvent( ),处理逻辑写在onTouchEvent( )。非要在 dispatchTouchEvent( ) 中消费的话,判断如果事件为 DOWN 不拦截。

  • 控件都没有消费 DOWN 事件,MOVE、UP则不会传递进来(Activity处理了)。

override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
    //子控件请求父控件的onInterceptTouchEvent()不要拦截DOWN事件
    parent.requestDisallowInterceptTouchEvent(true)
    return super.dispatchTouchEvent(event)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值