美文网首页
Android-你必须要懂得事件分发全解析

Android-你必须要懂得事件分发全解析

作者: 沉淀者 | 来源:发表于2020-05-14 18:05 被阅读0次

    一、 事件分发的对象是谁?

    答:事件

    当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
    Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
    主要发生的Touch事件有如下四种:

    • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)(只会产生一次)
    • MotionEvent.ACTION_MOVE:滑动View
    • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
    • MotionEvent.ACTION_UP:抬起View(与DOWN对应)(只会产生一次)

    事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:

    事件执行顺序.png

    二、事件分发的本质

    答:将点击事件(MotionEvent)向某个View进行传递并最终得到处理

    即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。

    三、事件在哪些对象之间进行传递?

    答:Activity、ViewGroup、View

    一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
    Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的

    image.png

    View是所有UI组件的基类

    一般Button、ImageView、TextView等控件都是继承父类View

    ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),

    其本身也是从View派生的,即ViewGroup是View的子类
    是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。

    与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。

    四、 事件分发过程由哪些方法协作完成?

    答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

    image.png

    五、 总结

    Android事件分发机制的本质是要解决:

    点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。

    这里的对象是指Activity、ViewGroup、View
    Android中事件分发顺序:Activity(Window) -> ViewGroup -> View

    事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成

    经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。

    六、事件分发机制方法介绍

    Android事件分发流程如下:(必须熟记)
    Android事件分发顺序:Activity(Window) -> ViewGroup -> View

    事件分发顺序.png

    super:调用父类方法(在这里Activity的父类是ViewGroup,ViewGroup的父类是View)
    true:消费事件,即事件不继续往下传递
    false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理

    看图总结:首先当你触摸的屏幕的时候会一口气产生至少2个事件,一个down,一个up,其余就是你手指移动过程中产生的move事件。首先down事件会先执行activity的dispatchTouchEvent方法,不管它返回true或者false都会直接在这里被消费,不会继续往下传递。只有调用父类方法的时候会执行ViewGroup的dispatchTouchEvent方法,此时假如dispatchTouchEvent返回true也代表在此消费,不会继续传递,假如返回false,表示不分发,则交还给Activity,说大家都处理不了,你自己处理吧。假如dispatchTouchEvent返回父类,则会调用ViewGroup的onInterceptTouchEvent,返回true表示拦截这个事件,交给自己的onTouchEvent方法消费事件,假如返回false,表示不拦截,则交给View的dispatchTouchEvent方法,同样的做处理,之后交给View的onTouchEvent方法,返回true代表消费,返回false,表示无法处理,交还给上级。

    其实,这些不需要硬记,实在开发中遇到不知道哪里返回true,哪里返回false时,回来看看这个顺序。最主要的是多去用用就记住了。下面我们看一般的事件传递。

    七、一般事件传递

    image.png

    7.1 默认情况

    即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
    那么调用的是这3个方法的默认实现:调用父类的方法
    事件传递情况:

    从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
    再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()

    7.2处理事件

    假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。

    事件传递情况:

    DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件
    因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
    该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()

    7.3 拦截DOWN事件

    假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。 事件传递情况:

    DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
    调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
    该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
    该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。

    7.4 拦截DOWN的后续事件

    假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。

    DOWN事件传递到C的onTouchEvent方法,返回了true。
    在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
    后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
    后续事件将直接传递给B的onTouchEvent()处理
    后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
    C再也不会收到该事件列产生的后续事件。

    八、源码分析

    View中dispatchTouchEvent()的源码分析

    public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }
    

    从上面可以看出:

    只有以下三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法

    • 第一个条件:mOnTouchListener != null;
    • 第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
    • 第三个条件:mOnTouchListener.onTouch(this, event);

    下面,我们来看看下这三个判断条件:

    第一个条件:mOnTouchListener!= null

    //mOnTouchListener是在View类下setOnTouchListener方法里赋值的
    public void setOnTouchListener(OnTouchListener l) { 
    
    //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
        mOnTouchListener = l;  
    }
    

    第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED

    该条件是判断当前点击的控件是否enable
    由于很多View默认是enable的,因此该条件恒定为true
    

    第三个条件:mOnTouchListener.onTouch(this, event)

    回调控件注册Touch事件时的onTouch方法
    
    //手动调用设置
    button.setOnTouchListener(new OnTouchListener() {  
      @Override  
      public boolean onTouch(View v, MotionEvent event) {  
          return false;  
      }  
    });
    

    如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。

    如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

    下面看onTouchEvent(event)**的源码分析

    主要这里面调用到了performClick()方法,关注这个方法即可

    public boolean performClick() {  
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
        }  
        return false;  
    }
    

    只要mOnClickListener不为null,就会去调用onClick方法;
    那么,mOnClickListener又是在哪里赋值的呢?请继续看:

    public void setOnClickListener(OnClickListener l) {  
        if (!isClickable()) {  
            setClickable(true);  
        }  
        mOnClickListener = l;  
    }
    

    当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。

    结论

    onTouch()的执行高于onClick()

    每当控件被点击时:如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。

    onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
    如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;

    onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()

    onTouch()和onTouchEvent()的区别

    这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
    如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。

    相关文章

      网友评论

          本文标题:Android-你必须要懂得事件分发全解析

          本文链接:https://www.haomeiwen.com/subject/xhrxohtx.html