Android中View事件的分发第二篇

ps:由于现在这个公众号没有留言功能,如有其他错误、建议、疑问,可发至到邮箱[email protected],我会将错误的内容更正

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

前面写了一篇Android中View事件的分发第一篇对事件分发的一些源码进行分析以及写 demo 进行演示验证结论;这一篇文章还对事件的分发进一步的学习与探索。

一个事件序列是指从手指触摸屏幕的那时候起,到手指抬起的那时候结束,这一过程中会产生一系列的事件,事件序列最终以 down 事件开始,最终到 up 事件结束,中途会有 N 个 move 事件;在Android中View事件的分发第一篇这一篇文章中曾演示过,所以这里就不再进行演示。

一般情况下,一个事件序列只能被-个 View 拦截并且处理,一个事件序列中的事件是由一个 View 处理的,一个 View 元素如果拦截了 down 事件,那么同一个事件序列内的 move 等所有事件都会直接交给这个 View 处理,而且这个 View 的 onInterceptTouchEvent 方法只会被调用一次。下面我们举个例子(案例A);

案例A

(1)新建一个 kt 语言的类 MyView 并继承与 View:

class MyView : View {


    constructor(context: Context) : super(context) {


    }


    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {


    }


    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {


    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.d(A_Activity.TAG, "-----MyView--onTouchEvent")
        return false
    }
}

(2)新建一个 kt 语言的类 MyLinearLayout 并继承于 LinearLayout:

class MyLinearLayout: LinearLayout {
    constructor(context: Context): super(context) {


    }


    constructor(context: Context,attrs: AttributeSet): super(context,attrs) {


    }


    constructor(context: Context,attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) {


    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(A_Activity.TAG,"-----MyLinearLayout--onTouchEvent--ACTION_DOWN")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(A_Activity.TAG,"-----MyLinearLayout--onTouchEvent--ACTION_MOVE")
            }
            MotionEvent.ACTION_UP -> {
                Log.d(A_Activity.TAG,"-----MyLinearLayout--onTouchEvent--ACTION_UP")
            }
        }
        return true
    }


    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        if (ev?.action == MotionEvent.ACTION_DOWN) {
            Log.d(A_Activity.TAG,"-----MyLinearLayout----onInterceptTouchEvent")
            return true
        } else if (ev?.action == MotionEvent.ACTION_MOVE) {
            Log.d(A_Activity.TAG,"-----MyLinearLayout----onInterceptTouchEvent")
        }
        return false
    }
}

(3)新建一个 kt 语言类型的 Activity,名字叫 A_Activity:

class A_Activity : AppCompatActivity() {
    companion object {
        var TAG: String = "A_Activity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_a_)
    }
}

(4)A_Activity 对应的布局文件为 activity_a_.xml:



    


    

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

Android中View事件的分发第二篇_第1张图片

当我们触摸绿色的区域时,便打印如下日志:

07-21 09:28:53.559 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout----onInterceptTouchEvent
07-21 09:28:53.560 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_DOWN
                               
07-21 09:28:53.788 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:53.804 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:53.821 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:54.021 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_MOVE
07-21 09:28:54.048 11572-11572/com.xe.eventdemo2 D/A_Activity: -----MyLinearLayout--onTouchEvent--ACTION_UP

这时候发现 MyView 的 onTouchEvent方法没有被调用,在这个案例中,MyView 作为子元素,MyLinearLayout 作为父元素,MyLinearLayout 重写 onInterceptTouchEvent 方法并在 down 事件中返回了 true 就相当于对子元素 MyView 的事件进行拦截并该方法只调用了一次。

如果 View 不消费除 down 以外的其他事件,父元素也不消费除 down 以外的其他事件,那么这个点击事件就会消失,这些消失的点击事件最终会传递给 Activity 处理。好,下面我们来写一个 demo 来验证上面的这句话(案例B);

案例B

(1)新建一个 kt 语言的类 MyView2 并继承与 View:

class MyView2 : View {


    constructor(context: Context) : super(context) {


    }


    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {


    }


    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {


    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(B_Activity.TAG,"-----MyView2--onTouchEvent--ACTION_DOWN")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(B_Activity.TAG,"-----MyView2--onTouchEvent--ACTION_MOVE")
            }
            MotionEvent.ACTION_UP -> {
                Log.d(B_Activity.TAG,"-----MyView2--onTouchEvent--ACTION_UP")
            }
        }
        return super.onTouchEvent(event)
    }
}

(2)新建一个 kt 语言的类 MyLinearLayout2 并继承于 LinearLayout:

class MyLinearLayout2: LinearLayout {
    constructor(context: Context): super(context) {


    }


    constructor(context: Context, attrs: AttributeSet): super(context,attrs) {


    }


    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context,attrs,defStyleAttr) {


    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(B_Activity.TAG,"-----MyLinearLayout2--onTouchEvent--ACTION_DOWN")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(B_Activity.TAG,"-----MyLinearLayout2--onTouchEvent--ACTION_MOVE")
            }
            MotionEvent.ACTION_UP -> {
                Log.d(B_Activity.TAG,"-----MyLinearLayout2--onTouchEvent--ACTION_UP")
            }
        }
        return super.onTouchEvent(event)
    }
}

(3)新建一个 kt 语言类型的 Activity,名字叫 B_Activity:

class B_Activity : AppCompatActivity() {
    companion object {
        var TAG: String = "B_Activity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b_)
    }




    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(TAG,"-----B_Activity--onTouchEvent--ACTION_DOWN")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(TAG,"-----B_Activity--onTouchEvent--ACTION_MOVE")
            }
            MotionEvent.ACTION_UP -> {
                Log.d(TAG,"-----B_Activity--onTouchEvent--ACTION_UP")
            }
        }
        return super.onTouchEvent(event)
    }
}

(4)B_Activity 对应的布局文件为 activity_b_.xml:



    


    


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

Android中View事件的分发第二篇_第2张图片

然后我们用手指触摸绿色的区域,打印如下日志:

07-22 13:23:52.375 26535-26535/com.xe.eventdemo2 D/B_Activity: -----MyView2--onTouchEvent--ACTION_DOWN
07-22 13:23:52.375 26535-26535/com.xe.eventdemo2 D/B_Activity: -----MyLinearLayout2--onTouchEvent--ACTION_DOWN
07-22 13:23:52.376 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_DOWN
07-22 13:23:52.503 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.686 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.703 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.820 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_MOVE
07-22 13:23:52.853 26535-26535/com.xe.eventdemo2 D/B_Activity: -----B_Activity--onTouchEvent--ACTION_UP

真的像上面结论总结的一样,我们的案例中的 MyView2 和 父元素 MyLinearLayout2 的触摸事件只消费 down 事件时,其他事件会给 Activity 来处理。

ViewGroup 默认是不拦截事件的,也就是 ViewGroup 的onInterceptTouch-Event 方法返回值是 false。我们来看看 ViewGroup 的 onInterceptTouchEvent 方法;

public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

发现该方法返回的默认值是 false,只有在事件源类型是鼠标并且是 down 事件是鼠标点击按钮和是滚动条的手势时才返回 true。所以默认一般 ViewGroup 的 onInterceptTouchEvent 方法返回都为 false,也就是说默认不拦截事件。

View 没有 onInterceptTouchEvent 方法,如果有事件传递给它,也就是 dispatchTouchEvent 方法被调用,那么它的 onTouchEvent 方法就会被调用(如果没有 onTouch 方法,或者 onTouch 方法返回值为 false 的条件下)。我们来看看 View 的 dispatchTouchEvent 方法;

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        //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;
        }
        ......
        return result;
 }

发现 View 的 dispatchTouchEvent 方法有调用 onTouchEvent 方法。

View 的 onClick 方法会执行的前提是当前 View 是可点击的(也就是 clickable 和 enabled 属性只要有一个为 true),并且它收到了 down 和 up 的事件;如果重写了 View 的 onTouchEvent 方法,那么 down 和 up 事件的返回值必须为 super.onTouchEvent(event),才会调用 View 的 onClick 方法,具体什么原因可以看Android中View事件的分发第一篇,下面我们写一个 demo 来验证这点结论(案例C);

案例C

(1)新建一个 kt 语言的类 MyView3 并继承与 View:

class MyView3: View {


    constructor(context: Context) : super(context) {


    }


    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {


    }


    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {


    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_DOWN")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_MOVE")
                return true
            }
            MotionEvent.ACTION_UP -> {
                Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_UP")
            }
        }
        return super.onTouchEvent(event)
    }
}

(2)新建一个 kt 语言类型的 Activity,名字叫 C_Activity:

class C_Activity : AppCompatActivity() {
    companion object {
        var TAG: String = "C_Activity";
    }
    var mMyView3: MyView3? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_c_)
        mMyView3 = findViewById(R.id.my_view3);
        mMyView3!!.isEnabled = true
        mMyView3!!.isClickable = false
        mMyView3!!.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                Log.d(TAG,"--C_Activity *** onClick--")
            }
        })
    }
}

(3)C_Activity 对应的布局文件为 activity_c_.xml:



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

Android中View事件的分发第二篇_第3张图片

当我们用手指触摸时,日志打印如下所示,可以看到 onClick 方法被调用了:

07-26 08:58:33.206 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_DOWN
07-26 08:58:33.303 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.620 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.637 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.670 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 08:58:33.754 5056-5056/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_UP
07-26 08:58:33.758 5056-5056/com.xe.eventdemo2 D/C_Activity: --C_Activity *** onClick--

当我们把 mMyView3 都改一下,也就是 mMyView3!!.isEnabled = false,mMyView3!!.isClickable = false,然后在运行程序用手触摸屏幕,日志打印如下所示,发现 onClick 方法没有被调用:

07-26 09:03:16.440 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_DOWN
07-26 09:03:16.525 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:03:16.776 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:03:16.851 5856-5856/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_UP

当我们把 mMyView3 都改一下,也就是 mMyView3!!.isEnabled = true,mMyView3!!.isClickable = false,然后再改一下 MyView3 的 down 事件的返回值为 true,代码如下修改:

override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_DOWN")
                return true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_MOVE")
                return true
            }
            MotionEvent.ACTION_UP -> {
                Log.d(C_Activity.TAG,"-----MyView3--onTouchEvent--ACTION_UP")
            }
        }
        return super.onTouchEvent(event)
 }

程序运行后,用手触摸屏幕,日志打印如下,发现也是没有调用 onClick 方法:

07-26 09:07:43.443 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_DOWN
07-26 09:07:43.574 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:07:43.894 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_MOVE
07-26 09:07:43.896 6687-6687/com.xe.eventdemo2 D/C_Activity: -----MyView3--onTouchEvent--ACTION_UP

本篇文章写到这里就结束了,由于技术水平有限,文章中难免会有错误,欢迎大家批评指正,另外附上demo地址:https://github.com/Yubao1/EventDemo2.git

Android中View事件的分发第二篇_第4张图片

关注微信公众号,阅读更多文章

你可能感兴趣的:(android,java,安卓,android,studio,移动开发)