Android事件分发机制解析

看了大神们对android事件分发机制的解析,为了方便自己理解和记忆,特意写一篇博客。

目录

方法执行顺序

各方法简单说明

getParent().requestDisallowInterceptTouchEvent(true)


方法执行顺序

boolean dispatchTouchEvent(MotionEvent ev):事件分发,Activity类,View类都有的方法
boolean onInterceptTouchEvent(MotionEvent ev):事件拦截,只有ViewGroup类才有的方法
boolean onTouch(View v, MotionEvent event):这不是View自带的方法,要通过setOnTouchListener()来添加OnTouchListener然后进入的一个方法
boolean onTouchEvent(MotionEvent event):事件消费,Activity类,View类都有的方法

例子布局



        

            

        
    

布局很简单,就一个RelativeLayout里面放个自定义的LinearLayout,LinearLayout里面再放个自定义的Button。自定义了两个控件,因为要重写这两个控件里面的一些方法。 

大家都知道事件分发机制分为拦截、传递、消费。根据上面的布局,用文字表达,如一个按下的事件MotionEvent.ACTION_DOWN发生的时候,是RelativeLayout先拿到,然后看这个布局R君要不要拦截,不拦截就传递给LinearLayout布局的L君,L君不拦截就传递到B君,B君如果消费掉这次事件(就是处理了这次事件),那这个按下的事件就到此结束了。如果B君不消费,那这个事件就会回到L君,L君消费,就在L君那结束。L君不消费,事件就回到R君那里处理。

文字表达有点乱,下下面会有流程图。

Activity里就为各控件添加onTouch方法和打印日志。(以下是Activity部分代码,代码用的是ButterKnife)

    @OnTouch(R.id.btn_test1)
    public boolean onTouch1(View v, MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                //btnTest1.getParent().requestDisallowInterceptTouchEvent(true);
                // 手指按下
                Log.e("ousyxx", "downTest1");
                break;
            case MotionEvent.ACTION_MOVE:
                // 手指移动
                Log.e("ousyxx", "moveTest1");
                break;
            case MotionEvent.ACTION_UP:
                // 手指抬起
                Log.e("ousyxx", "upTest1");
                break;
            case MotionEvent.ACTION_CANCEL:
                // 事件被拦截
                Log.e("ousyxx", "cancelTest1");
                break;
        }

        return false;
    }

    @OnTouch(R.id.ll_test1)
    public boolean onLlTouch(View v, MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                // 手指按下
                Log.e("ousyxx", "downLlTest");
                break;
            case MotionEvent.ACTION_MOVE:
                // 手指移动
                Log.e("ousyxx", "moveLlTest");
                break;
            case MotionEvent.ACTION_UP:
                // 手指抬起
                Log.e("ousyxx", "upLlTest");
                break;
            case MotionEvent.ACTION_CANCEL:
                // 事件被拦截
                Log.e("ousyxx", "cancelLlTest");
                break;
        }

        return false;
    }

    @OnTouch(R.id.rl_test1)
    public boolean onRlTouch(View v, MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                // 手指按下
                Log.e("ousyxx", "downRlTest");
                break;
            case MotionEvent.ACTION_MOVE:
                // 手指移动
                Log.e("ousyxx", "moveRlTest");
                break;
            case MotionEvent.ACTION_UP:
                // 手指抬起
                Log.e("ousyxx", "upRlTest");
                break;
            case MotionEvent.ACTION_CANCEL:
                // 事件被拦截
                Log.e("ousyxx", "cancelRlTest");
                break;
        }
        return false;
    }

各方法简单说明

说明之前,大家先看看,假如你没重写过任何方法,就按照上面的布局,你点击那个MyButton控件,大概的事件传递流程如下

Android事件分发机制解析_第1张图片

 

dispatchTouchEvent:有事件发生的时候,先进入这个方法。

return false: 事件不会被再进行分发。事件会被传递回上一层的view的onTouch方法、onTouchEvent方法。如果view没有添加onTouchListener,那事件就会直接到onTouchEvent;

return true:该事件就停在这方法里处理,不会继续传递。

举个例子1:加入你在Activity类里重写了dispatchTouchEvent这个方法如下,返回super.dispatchTouchEvent(ev),就是执行你没重写原本Activity类自己原本的dispatchTouchEvent方法,当mTouchType为true时就会进入return true,这时这个Activity上就什么事件都不会发生,什么点击滑动都没有,因为事件在该Activity你重写dispatchTouchEvent里已经结束了这次事件。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        if (mTouchType)
        {
            return true;
        }

        return super.dispatchTouchEvent(ev);
    }

举个例子2:下面是我自定义LinearLayout的代码,在dispatchTouchEvent里直接return false,那么事件就会回到R君那里,流程图大概如下

public class MyLinearLayout extends LinearLayout
{
    // 是否拦截
    private boolean mIsIntercept = false;
    public MyLinearLayout(Context context, @Nullable AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        if (mIsIntercept)
            return true;

        return super.onInterceptTouchEvent(ev);
    }
}

Android事件分发机制解析_第2张图片

onInterceptTouchEvent:所有ViewGroup类正常的情况下经过上面的方法后都会进入此拦截方法。
return false:不拦截,事件传递给子view
return true: 拦截,事件会在该层处理
上面的代码,我重新写了这个方法,假如上面的代码我没有重写dispatchTouchEvent,而是重写onInterceptTouchEvent,让它return true,流程图就如下

Android事件分发机制解析_第3张图片

onTouch:控件自己setOnTouchListener添加的方法

return false: 不消费此事件,事件会传到该控件的onTouchEvent方法

return true:消费此事件,事件不会传到该控件的onTouchEvent方法,事件就此结束

onTouchEvent:要不要消费此事件的方法

return false: 不消费此事件,事件就会返回到自己的父View的onTouch继续处理

return true:消费此事件,事件就此结束

举个例子:假如你重写了Button类的onTouchEvent,让它return false,那么流程图大概如下

Android事件分发机制解析_第4张图片

 

getParent().requestDisallowInterceptTouchEvent(true)

 这个方法在关于事件分发的开发中可能会见到,所以也记录一下。一般大家可能看到L君的onInterceptTouchEvent的拦截方法的重写是如下这样。可以看到,L君只在ACTION_MOVE处才return true,就是说但你按下B君控件时,按下的事件是进入了B君的onTouch1。而当你按下、滑动的时候,滑动事件就被L君拦截,那么滑动事件就会进入L君的onLlTouch。

但从打印的日志可以看到,同时也有个事件进入了B君的onTouch1,那就是MotionEvent.ACTION_CANCEL。没错当B君的事件中途被父View拦截后,就会有ACTION_CANCEL事件进入B君的onTouch1。

@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 false;
            default:
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

这时如果B君不想这移动的事件被拦截的话,就可以使用getParent().requestDisallowInterceptTouchEvent(true)请求父View即L君不要拦截B君接下来的事件。可以看看代码如下:

@OnTouch(R.id.btn_test1)
    public boolean onTouch1(View v, MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                btnTest1.getParent().requestDisallowInterceptTouchEvent(true);
                // 手指按下
                Log.e("ousyxx", "downTest1");
                break;
            case MotionEvent.ACTION_MOVE:
                // 手指移动
                Log.e("ousyxx", "moveTest1");
                break;
            case MotionEvent.ACTION_UP:
                // 手指抬起
                Log.e("ousyxx", "upTest1");
                break;
            case MotionEvent.ACTION_CANCEL:
                // 事件被拦截
                Log.e("ousyxx", "cancelTest1");
                break;
        }

        return false;
    }

如1代码所示,在B君接收到ACTION_DOWN按下事件时执行了请求,那么接下来移动的事件还是会进入到B君的onTouch1了。

完!

你可能感兴趣的:(Android事件分发机制解析)