https://mp.weixin.qq.com/s/i95HCZ_In8wfvkY2qGVjow

ps:阅读原文可获取demo源码,文章末尾有原文链接
ps:源码是基于 android api 27 来分析的,demo 是用 kotlin 语言写的
在 Android View 事件的分发中,如果 View 的 dispatchTouchEvent方法被调用,那么它就不会再往下分发,也不会进行拦截,因为 View 是最底层的元素;在事件分发中,View 是充当子元素的,而不能充当父元素,它的2个方法发挥着很好的协作能力,那就是 dispatchTouchEvent 方法和 onTouchEvent 方法;首先我们先看 dispatchTouchEvent 方法:
public boolean dispatchTouchEvent(MotionEvent event) {

// If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { //1、 // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); }boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); }final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { //2、 // Defensive cleanup for new gesture stopNestedScroll(); }//3、 if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //4、 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //5、 if (!result && onTouchEvent(event)) { result = true; } }if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); }//6、 // 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; }

注释1 表示如果不能获得焦点或者不存在一个 View,那么就不处理事件;注释2 表示如果我们点击 View时,其他的依赖滑动都要先停下;注释3 表示过滤掉一些不合理的事件,比如说弹出一个 Dialog 把 View 给挡住了;注释4 表示(1)首先判断监听 ListenerInfo 对象不为 null 且我们通过 setOnTouchListener 设置了监听,即是实现 OnTouchListener 接口的 onTouch 方法,(2)如果有实现就判断当前的 View 状态是不是 ENABLED(enabled 属性是否为 true),(3)如果实现的 OnTouchListener 的 onTouch 中返回true,这(1)(2)(3)同时成立表示处理事件并调用;注释5 表示如果注释4 的条件不成立,那么执行注释5 的代码,即触摸事件,说明 OnTouchListener 的 onTouch 方法优先级比 onTouchEvent 方法的优先级高,在Android中View事件的分发第一篇这篇文章已验证;注释6 表示如果这是手势的结尾,则在嵌套滚动后清理。
回过头来看,注释5 的代码,本篇文章中的另外一个角色 onTouchEvent 方法;
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); //7、 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //8、 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }//9、 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: ...... //10、 performClick(); ...... break; } }return true; }return false;

}
注释8 表示如果是 DISABLED,也就是 View.setEnabled(false),那么返回注释7 的布尔值,注释7后面再分析;注释9 表示如果可以点击或者此视图可以在悬停又或长按时显示工具提示,那么就返回 true,也就是消费事件;注释10 表示当手指抬起来的时候会调用 OnClickListener.onClick 方法(可以点击 performClick 方法看看,如果当前的 View 重写了 onTouchEvent 方法,那么它的 up 事件返回值必须是 super.onTouchEvent(event)),如果当前 View 设置有 OnClickListener 事件的话。
注释7 表示是否可以点击,由当前 View 的 CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE 决定,也就是 clickable、longClickable 和 contextClickable 属性是否为 true;View 的 longClickable 属性默认都为 false,clickable 属性要分情况,如果是 Button 的 clickable 属性默认为 true,而 TextView 的 clickable 属性默认为 false。
【https://mp.weixin.qq.com/s/i95HCZ_In8wfvkY2qGVjow】View(设置有 OnClickListener 事件) 的 enable(ENABLED 值) 属性不影响 super.onTouchEvent 的默认返回值(返回为 true),即使 View 是 disable(DISABLED 值) 状态的,只要它的 clickable 或者 longClickable 或者 contextClickable 属性有一个为 true,那么它的 super.onTouchEvent 就返回 true,但是如果 View 真是 disable(DISABLED 值) 状态的并且设置有 OnClickListener 事件,那么 OnClickListener.onClick 方法不会被回调;又如果 View 的 clickable、 longClickable 和 contextClickable 属性都为 false,enable 属性为 true 并且设置有 OnClickListener 事件,那么 OnClickListener.onClick 方法会被回调, super.onTouchEvent(event) 的返回值也会为 true。
我们来验证蓝色文字这一段话,下面我们来写一个 demo;
(1)新建一个 kotlin 语言类型的 Activity,名叫 MainActivity:
class MainActivity: AppCompatActivity() {
companion object { var TAG: String = "MainActivity" } var mView: View? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mView = findViewById(R.id.my_view) mView?.isLongClickable = false mView?.isContextClickable = false mView?.isClickable = true mView?.isEnabled = true mView?.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { Log.d(TAG,"----onClick----") } }) }

}
(2)新建一个 kotlin 语言类型的类 MyView 并继承于 View:
class MyView: View {
constructor(context: Context): super(context) {} constructor(context: Context,@Nullable attrs: AttributeSet): super(context,attrs) {} constructor(context: Context, @Nullable attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) {}override fun onTouchEvent(event: MotionEvent?): Boolean { var consume: Boolean = super.onTouchEvent(event) when(event?.action) { MotionEvent.ACTION_DOWN -> { Log.d(MainActivity.TAG,"--MotionEvent.ACTION_DOWN--isConsume = " + consume) } MotionEvent.ACTION_MOVE -> { Log.d(MainActivity.TAG,"--MotionEvent.ACTION_MOVE--isConsume = " + consume) } MotionEvent.ACTION_UP -> { Log.d(MainActivity.TAG,"--MotionEvent.ACTION_UP--isConsume = " + consume) } } return consume }

}
(3)新建 MainActivity 对应的布局文件 activity_main:

xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/my_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00FF00" tools:context="com.xe.eventdemo4.MainActivity">


程序一开始运行的界面如下所示:
图片
我们触摸一下绿色的屏幕,日志打印如下所示:
08-24 13:19:23.085 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_DOWN--isConsume = true
08-24 13:19:23.143 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:19:23.210 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:19:23.227 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:19:23.293 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:19:23.310 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:19:23.460 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:19:23.506 15226-15226/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_UP--isConsume = true
08-24 13:19:23.517 15226-15226/com.xe.eventdemo4 D/MainActivity: ----onClick----
从日志看出 contextClickable、longClickable 和 clickable 属性只要有一个为 true 时,super.onTouchEvent(event) 就返回为 true。
我们只把 enable 属性改为false, 即 mView?.isEnabled = true 改为 mView?.isEnabled = false,其他代码不变,然后运行程序,用手指触摸绿色屏幕,日志打印如下所示:
08-24 13:27:31.960 18435-18435/com.xe.eventdemo4 W/Activity: Slow Operation: Activity com.xe.eventdemo4/.MainActivity onCreate took 586ms
08-24 13:27:37.732 18435-18435/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_DOWN--isConsume = true
08-24 13:27:37.807 18435-18435/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:27:37.858 18435-18435/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:27:37.875 18435-18435/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:27:37.958 18435-18435/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:27:38.019 18435-18435/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_UP--isConsume = true
从日志看出 contextClickable、longClickable 和 clickable 属性只要有一个为 true 时,即使 enable 属性为false,super.onTouchEvent(event) 返回值还是 true,同时 OnClickListener.onClick(该方法被调用的前提是 enable 属性为true) 方法不会被调用。
我们把 clickable 属性改为 false,enable 属性改为 true,运行程序,用手指触摸绿色屏幕,日志打印如下所示:
08-24 13:56:03.837 20118-20118/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_DOWN--isConsume = true
08-24 13:56:03.949 20118-20118/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:56:04.189 20118-20118/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_MOVE--isConsume = true
08-24 13:56:04.284 20118-20118/com.xe.eventdemo4 D/MainActivity: --MotionEvent.ACTION_UP--isConsume = true
08-24 13:56:04.292 20118-20118/com.xe.eventdemo4 D/MainActivity: ----onClick----
从日志可以看出 super.onTouchEvent(event) 返回的是 true,OnClickListener.onClick 方法也会被调用。

    推荐阅读