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

2021年09月15日 阅读数:4
这篇文章主要向大家介绍https://mp.weixin.qq.com/s/i95HCZ_In8wfvkY2qGVjow,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

ps:阅读原文可获取demo源码,文章末尾有原文连接android

ps:源码是基于 android api 27 来分析的,demo 是用 kotlin 语言写的api

在 Android View 事件的分发中,若是 View 的 dispatchTouchEvent方法被调用,那么它就不会再往下分发,也不会进行拦截,由于 View 是最底层的元素;在事件分发中,View 是充当子元素的,而不能充当父元素,它的2个方法发挥着很好的协做能力,那就是 dispatchTouchEvent 方法和 onTouchEvent 方法;首先咱们先看 dispatchTouchEvent 方法:ide

public boolean dispatchTouchEvent(MotionEvent event) {工具

   
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        //一、
        // 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) {
        //二、
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    //三、
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        
        //四、
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //五、
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    //六、
    // 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 方法;this

public boolean onTouchEvent(MotionEvent event) {spa

    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    //七、
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    //八、
    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;
        }
    }

    //九、
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ......
                //十、
                performClick();
                ......
                break;
            }
        }

        return true;
    }

    return false;

}rest

注释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。code

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