View的点击事件分发机制

点击事件的分发,其实就是对MotionEvent事件的分发。
当事件产生后,系统会把这个事件传递到某个具体的View,这个传递的过程是由三个很重要的方法共同完成。

dispatchTouchEvent:
    进行事件的分发,如果事件传递到了该View,那么此方法一定会被调用。
    返回的结果受到当前View的onTouchEvent和下级View的onInterceptTouchEvent的影响。

onInterceptTouchEvent:
    处理是否拦截某个事件,如果当前的View拦截了,那么同一个事件序列中,此方法不会被调用。返回结果表示是否拦截该事件。
    同一个事件序列是指手指从按下到手指离开屏幕,在这个过程所产生的一系列事件。
    DOWN事件开始,很多MOVE事件,UP事件结束。   

onTouchEvent:
    处理点击事件,返回的结果表示是否消耗当前的事件。如果不消耗,则在同一事件序列中,当前的View无法再次接收到事件。   
结合上面的三个方法说一下大体的过程:

当点击事件产生了
事件最先传递给Activity,Activity再传递到Window,Window再传递到最顶层的View。
而最顶层的View接收到事件就会进行事件的分发。

顶层View,一般都是ViewGroup的dispatchTouchEvent调用,用于处理事件的分发,
如果onInterceptTouchEvent返回true,表示拦截此事件,那么该ViewGroup的onTouchEvent就会调用。
如果onInterceptTouchEvent返回false,那么表示它不拦截该事件。
这个事件就会传递到它的子元素,而子元素同样也会重复该过程。直到事件被最终处理。

一,Activity对事件分发的过程
Activity—->Window—–>最顶层的View

这个过程究竟是怎么样的?

点击事件产生了
首先Activity的dispatchTouchEvent进行事件的分发:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

(getWindow返回的是Window对象)
会把MotionEvent事件交给Window的superDispatchTouchEvent去处理。
查看Window的源码得知,Window是个抽象类,Window的实现类是PhoneWindow。

指定到PhoneWindow的superDispatchTouchEvent方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
}

(mDecor是DecorView类型的变量)
PhoneWindow又会把事件传递到DecorView中。
而DecorView继承自FrameLayout。FrameLayout继承自View,DecorView它就是最顶层的View。
至此事件由Activity---->Window----->最顶层的View的过程就完成了。

现在写了一个十分简单的布局来理解上面所说的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        style="@style/Botton_Common"
        android:id="@+id/btn_next"
        android:text="下一个"/>
</LinearLayout>

然后在Activity的setContenView中去加载这个layout。
它的层级关系是下面这样的:

这里写图片描述

DecorView正是位于最顶层,而我们布局的View,是它的子View。
可以通过
(ViewGroup)getWindow().getDecorView().findViewById(R.id.content).getChild(0)来获得布局的View。

二,顶层View对点击事件的分发

最顶层View的事件分发过程:
最顶层View一般都是一个ViewGroup,上面提到的DecorView就是ViewGroup。

最顶层View的dispatchTouchEvent调用负责事件分发,
onInterceptTouchEvent返回true的情况:
如果设置了setOnTouchListener(此时的mOnTouchListener不为null),就会调用onTouch方法。onTouch返回false,onTouchEvent将会被调用,返回true,onTouchEvent不会调用。
如果onTouchEvent中设置了setOnClickListener(此时的mClickListener不为null),那么onClick会被调用。

onInterceptTouchEvent返回false的情况:(不拦截)
则事件会传递到它所在点击事件链上的子View。
这时事件已经从顶层View传递到了子View中。

传递到子View会继续循环上面的过程。

最顶层View的dispatchTouchEvent
查看ViewGroup的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = 
                        (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
}

先看这个条件
(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)

MotionEvent.ACTION_DOWN表示的是DOWN事件。
当事件成功的由ViewGroup的子View处理后,mFirstTouchTarget会被赋值,不为null。
ViewGroup不拦截事件,交由ViewGroup的子View去处理后,mFirstTouchTarget!=null成立。

那么当一个MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP事件到来时,上面那个条件就不成立。
那么onInterceptTouchEvent将不会再被调用。

那如果只面对MotionEvent.ACTION_DOWN事件,那么ViewGroup的onInterceptTouchEvent会被调用,拦截。

一种特殊情况的考虑:
FLAG_DISALLOW_INTERCEPT标记位,它会在子View的requestDisallowInterceptTouchEvent方法中进行设置。
它一旦设置,ViewGroup将不拦截事件,但除了ACTION_DOWN事件。

为什么是除了ACTION_DOWN?
因为ACTION_DOWN会重置FLAG_DISALLOW_INTERCEPT标记位。在子View中设置是没有用处的。

if(actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();//重置FLAG_DISALLOW_INTERCEPT
}
一个结论:
当ViewGroup决定拦截事件,那么后续的点击事件都会交给它处理,并且不再调用它的onInterceptTouchEvent。

我的解释(可能有误):
当ViewGroup拦截了DOWN事件后,调用onInterceptTouchEvent去拦截处理,
那么后来的MOVE,UP事件过来时就不会通过
(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)这个判断。
那么表示后续的点击事件默认都会交给该ViewGroup去处理,并且不再调用onInterceptTouchEvent。


另一个结论:
在子View中可以通过requestDisallowInterceptTouchEvent方法
来设置FLAG_DISALLOW_INTERCEPT标记位。
来干预父View的事件分发处理,但是DOWN事件除外。

总结:
①onInterceptTouchEvent并不是每个事件都会被调用。如果我们想提前处理所有的点击事件,那么在
dispatchTouchEvent去处理。
(后面这个总结不太理解)

②可以利用子View来标记FLAG_DISALLOW_INTERCEPT来干扰父View的事件分发过程。

------未完待续......----------
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值