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。

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:

<?xml version="1.0" encoding="utf-8"?>
<com.xe.eventdemo4.MyView

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">

</com.xe.eventdemo4.MyView>

程序一开始运行的界面如下所示:

图片

我们触摸一下绿色的屏幕,日志打印如下所示:

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 方法也会被调用。

阅读 216

活到老,学到老。

1 声望
1 粉丝
0 条评论
你知道吗?

活到老,学到老。

1 声望
1 粉丝
宣传栏