Android事件传递之onInterceptTouchEvent()和requestDisallowInterceptTouchEvent()方法的使用

之前看郭神的事件分发机制解析博客,深入的从源码角度学习了一下Android的事件分发机制。

如果还没看过,那么请先去郭神的博客学习下:http://blog.csdn.net/guolin_blog/article/details/9097463

了解到事件从ViewGroup向子View分发的过程中,需要走一个判断,如果这个判断语句没有过,那么事件将被父布局给拦截掉,这个判断语句是:

if(disallowIntercept || !onInterceptTouchEvent(ev))

其中disallowIntercept这个标志表示是否禁用事件拦截功能,默认情况都是false,所以一般情况下,是否拦截事件就得看后面的!onInterceptTouchEvent(ev) 这个语句的正负值了,假设我们把这里的onInterceptTouchEvent(ev) 重写并始终返回为true,那么子view将会永远得不到事件的分发,所有的事件都会被ViewGroup拦截。

平时开发过程中可能会有这种需求:在触控的时候,按下那下(ACTION_DOWN)需要子View进行事件反馈,但在滑动(ACTION_MOVE)或者抬起(ACTION_UP)的时候需要父布局进行拦截操作,这个时候就要用到我们的requestDisallInterceptRouchEvent(boolean disallowIntercept)方法了,该方法可以在子类通过调用getParent().requestDisallInterceptRouchEvent(true) 来让父布局禁用拦截事件功能,从而父布局忽略该事件之后的一切Action,话不多说,来看看代码:

首先先自定义一个布局,继承自LinearLayout,同时我们重写了onInterceptTouchEvent方法,让父类拦截ACTION_MOVE和ACTION_UP,如下所示:

public class MyLayout extends LinearLayout{
    public MyLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return false;
            case MotionEvent.ACTION_MOVE:   //表示父类需要
                    return true;
            case MotionEvent.ACTION_UP:
                return true;
            default:
                break;
        }
        return false;    //如果设置拦截,除了down,其他都是父类处理
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("TAG", "You down layout");
                break;
            case MotionEvent.ACTION_UP:
                Log.d("TAG", "You up layout");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("TAG", "You move layout");
        }
        return true;
    }
}

然后,我们再自定义一个按钮,同时重写它的onTouchEvent()方法,打印一下action的log,同时我们在ACTION_DOWN中加上 getParent().requestDisallowInterceptTouchEvent(true),表示在DOWN的时候屏蔽父类的事件屏蔽,如下所示:

public class MyButton extends Button {
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                Log.d("TAG", "You down button");
                break;
            case MotionEvent.ACTION_UP:
                Log.d("TAG", "You up button");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("TAG", "You move button");
        }
        return true;
    }
}

这里需要提一点,retuan true 是告诉系统当前的View需要处理这次的touch事件,以后的系统发出的ACTION_MOVE,ACTION_UP还是需要继续监听并接收的,而且这次的action已经被这个View给消费掉了,父层的ViewGroup将不会走onTouchEvent了。所以每一个action最多只能有一个onTouchEvent接口返回true。如果return false,这个action会传向父级,调用父级View的onTouchEvent。但是这一次的touch事件之后发出的任何action,该View都不会再接受,onTouchEvent在这一次的touch事件中再也不会触发,也就是说一旦View返回false,那么之后的ACTION_MOVE,ACTION_UP等ACTION就不会在传入这个View,但是下一次touch事件的action还是会传进来的。所以在这边把返回值统一置为true,避免子View主动的将事件回传给父层ViewGroup

打开主布局文件activity_main.xml,加入我们刚刚写的自定义布局:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.weaponzhi.viewgroupinterrupteventtest.MainActivity">

    <com.weaponzhi.viewgroupinterrupteventtest.MyLayout
        android:id="@+id/myLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.weaponzhi.viewgroupinterrupteventtest.MyButton
            android:id="@+id/myButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="测试onTouchEvent" />
    com.weaponzhi.viewgroupinterrupteventtest.MyLayout>
LinearLayout>

运行时候,效果图如下所示:

Android事件传递之onInterceptTouchEvent()和requestDisallowInterceptTouchEvent()方法的使用_第1张图片

这个时候,我们点击一下按钮,再滑动一下按钮,控制台打印如下:

Android事件传递之onInterceptTouchEvent()和requestDisallowInterceptTouchEvent()方法的使用_第2张图片

你会发现,父布局的事件全部都被子View消费掉了,即使父布局在UP和MOVE的时候已经明确的把onInterceptTouchEvent方法返回为true。

细致的分析下出现这种情况的原因,首先,根据源码,事件传递过来后,先进入父布局的disPatchTouchEvent()方法,第一个Action(ACTION_DOWN)的时候,首先调用了onInterceptTouchEvent方法,这个时候,还是返回的是false,所以事件被传递到了子View中,子View接收到后,第一时间将父布局的disallowIntercept 设置为true,所以当本次touch事件之后的action再次进入到父布局disPatchTouchEvent的时候,条件语句if(disallowIntercept || !onInterceptTouchEvent(ev)) 始终为true,并且将忽略到后面!onInterceptTouchEvent(ev) 的判断,导致之后的一切action都将被分发到子View中,除非在之后子View的onTouchEvent中将该disallowIntercept重新设置为false。

这里需要注意的是,因为父布局的onInterceptTouchEvent()方法会比该次事件下子View的onTouchEvent()方法要更早进行(如果该事件会分发给子View的话),所以,如果子View设置getParent().requestDisallowInterceptTouchEvent(true)代码的那次事件的onInterceptTouchEvent()返回的是true,那么父布局依然会拦截事件,子View的这次禁用父布局拦截事件请求将会失败。

例如,如果像下面代码那样,在ACTION_DOWN的时候onInterceptTouchEvent返回true,即使子View的onTouchEvent()始终请求禁用拦截,父布局依然会拦截到所有的事件:

  public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;//父布局拦截事件
            case MotionEvent.ACTION_MOVE:   //表示父类需要
                    return false;
            case MotionEvent.ACTION_UP:
                return false;
            default:
                break;
        }
        return false;    //如果设置拦截,除了down,其他都是父类处理
    }

这是这种情况下,子View的onTouchEvent()方法:

public boolean onTouchEvent(MotionEvent event) {
    //如果子View被分发了事件,请求父布局不要在之后拦截事件
    getParent().requestDisallowInterceptTouchEvent(true);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("TAG", "You down button");
                break;
            case MotionEvent.ACTION_UP:
                Log.d("TAG", "You up button");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("TAG", "You move button");
        }
        return true;
    }

运行后,进行之前的操作,控制台如下:

Android事件传递之onInterceptTouchEvent()和requestDisallowInterceptTouchEvent()方法的使用_第3张图片

你会发现,事件已经完全被父层拦截了

源码地址:https://github.com/WeaponZhi/ViewGroupInterruptEventTest/

你可能感兴趣的:(Android开发)