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
        android:id="@+id/rl_test1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00ffff">

        <com.ousy.touchtest.MyLinearLayout
            android:id="@+id/ll_test1"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#00ff00"
            android:orientation="vertical">

            <com.ousy.touchtest.MyButton
                android:id="@+id/btn_test1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="test1"/>

        </com.ousy.touchtest.MyLinearLayout>
    </RelativeLayout>

布局很简单,就一个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控件,大概的事件传递流程如下

流程图

 

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);
    }
}

流程图

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

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

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

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

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

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

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

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

 

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了。

完!

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Android 事件分发机制是指在用户与Android设备进行交互时,Android系统如何接收并分发这些事件的过程。事件分发机制包括三个阶段:分发、拦截和处理。 1. 分发阶段:事件Android设备的底层硬件驱动程序开始,通过InputEvent分发给View层。在View层中,事件分为两类:MotionEvent和KeyEvent。MotionEvent表示触摸事件,包括按下、移动、抬起等操作;KeyEvent表示按键事件,包括按下和抬起。 2. 拦截阶段:在事件分发到View层后,会从最上层的View开始进行事件分发,直到有View对事件进行拦截。如果有View对事件进行了拦截,则事件不会继续向下分发,而是由该View进行处理。View是否拦截事件的判断由onInterceptTouchEvent方法完成,如果该方法返回true则表示拦截事件。 3. 处理阶段:如果事件没有被拦截,则会被传递到最底层的View进行处理。在View中,事件处理由onTouchEvent方法完成。如果该方法返回true,则表示事件已经被处理,不再需要继续向下分发;如果返回false,则会继续向上分发直到有View对事件进行拦截。 Android事件分发机制的流程如下: ![image.png](attachment:image.png) 需要注意的是,事件分发机制是一个逆向分发的过程,即从底层向上分发,而不是从顶层向下分发。这是因为底层的View需要先处理事件,如果底层的View不拦截事件事件才能向上分发

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值