文章目录
Android 事件分发 系列文章目录
前言
一、View 的事件传递机制 ( dispatchTouchEvent )
二、触摸事件 与 点击事件 冲突处理
三、View 事件分发相关源码
前言
接上一篇博客 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 ) , 继续分析 ViewGroup 的事件分发机制后续代码 ;
一、View 的事件传递机制 ( dispatchTouchEvent )
在上一篇博客 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 ) 二、当前遍历的子组件的事件分发 章节 , 分析到 ViewGroup 的 dispatchTouchEvent 方法中的最终事件分发 , 调用到了 View 的 dispatchTouchEvent 方法继续向子组件分发触摸事件 ;
View 组件设置 点击监听器 View.OnClickListener , 触摸监听器 View.OnTouchListener , 都设置在 View 的 View.ListenerInfo 类型成员中 ;
判断该组件是否被用户设置了 触摸监听器 OnTouchListener , 如果设置了 , 则执行被用户设置的 触摸监听器 OnTouchListener ;
如果用户设置的 触摸监听器 OnTouchListener 触摸方法返回 true , 此时该分发方法的返回值就是 true ;
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { public boolean dispatchTouchEvent(MotionEvent event) { //noinspection SimplifiableIfStatement // 设置的 触摸监听器 就是封装在该对象中 ListenerInfo li = mListenerInfo; // 判断该组件是否被用户设置了 触摸监听器 OnTouchListener if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED // 执行被用户设置的 触摸监听器 OnTouchListener && li.mOnTouchListener.onTouch(this, event)) { // 如果用户设置的 触摸监听器 OnTouchListener 触摸方法返回 true // 此时该分发方法的返回值就是 true result = true; } } }
如果上述 li.mOnTouchListener.onTouch(this, event) 执行的触摸监听器触摸方法返回值为 true , 则不会调用 View 组件自己的 onTouchEvent 方法了 , 在 onTouchEvent 方法中会调用 点击监听器的方法 ;
如果用户的 触摸监听器 OnTouchListener 返回 true , 则 用户的 点击监听器 OnClickListener 会被屏蔽掉 ;
如果同时设置了 点击监听器 OnClickListener 和 触摸监听器 OnTouchListener , 此时需要做 触摸事件 与 点击事件的兼容处理 ;
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { public boolean dispatchTouchEvent(MotionEvent event) { // 如果上面为 true ( 触摸监听器的触摸事件处理返回 true ) , 就会阻断该分支的命中 , 该分支不执行了 // 也就不会调用 View 组件自己的 onTouchEvent 方法 // 因此 , 如果用户的 触摸监听器 OnTouchListener 返回 true // 则 用户的 点击监听器 OnClickListener 会被屏蔽掉 // 如果同时设置了 点击监听器 OnClickListener 和 触摸监听器 OnTouchListener // 触摸监听器 OnTouchListener 返回 false , 点击监听器 OnClickListener 才能被调用到 if (!result && onTouchEvent(event)) { result = true; } } }
View 的 onTouchEvent 方法分析 :
Click 点击事件 , 是一次完整的按下 + 抬起操作 , 如果要判定点击 , 需要同时有 MotionEvent.ACTION_DOWN + MotionEvent.ACTION_UP 事件 , 因此这里在 MotionEvent.ACTION_UP 事件分支中查找点击事件 ;
最终找到了点击事件的调用方法 performClickInternal 方法 ;
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { public boolean onTouchEvent(MotionEvent event) { if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: // 点击事件 Click 是 按下 + 抬起 事件 // 如果要判定点击 , 需要同时有 MotionEvent.ACTION_DOWN + MotionEvent.ACTION_UP 事件 if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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)) { performClickInternal(); } } } } }
在 performClickInternal 方法中, 调用了 performClick 方法 ;
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { private boolean performClickInternal() { // Must notify autofill manager before performing the click actions to avoid scenarios where // the app has a click listener that changes the state of views the autofill service might // be interested on. notifyAutofillManagerOnClick(); return performClick(); } }