美文网首页Android开发Android技术知识Android开发
Android——彻底搞懂事件分发机制

Android——彻底搞懂事件分发机制

作者: softSnowzzz | 来源:发表于2019-11-29 16:22 被阅读0次

    前言

    事件分发,总觉得不好理解,感觉非常麻烦,因为它涉及到的东西实在太多了,到底怎么分发与以下因素都有关:在哪个视图层级(Activity、ViewGroup、View),什么事件类型(DOWN、MOVE、UP、CANCEL),在哪个回调方法(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent),回调方法返回不同的返回值(true、false)。这些因素都会影响事件的分发,如果单纯死记硬背,就算当时背过了,过一段时间也就忘了,所以,真正理解事件分发,才是搞懂事件分发的关键。要理解事件分发,那我们就需要弄清楚,为什么需要事件分发,它具体又是做了什么呢?

    思考

    • 事件是什么?
      我们从手指接触屏幕那一刻起,到手指抬起,离开屏幕,在这个过程中,可能还伴随着在屏幕上滑动,这就是一个完整的事件序列,也就是说一个完整的事件序列就包括:按下、滑动或不滑动、抬起。也就对应着DOWN、0个或多个MOVE、UP,这是我们最直观感受得出的结论。
    • 事件分发机制是为了解决什么问题?
      其实仔细想想,事件分发,其实就是事件的传递与处理。事件,由我们人为产生,产生了,系统就需要给我们反馈,这也就是事件传递与处理的过程。
    • 事件分发的传递方向?
      我们触摸屏幕,得到触摸事件,而我们看到的界面是Activity,也就是说,最开始得到触摸事件的是Activity,View又会被层层ViewGroup包裹,那么事件的传递方向就应该是Activity->ViewGroup->View。

    事件分发实现

    我们现在知道,事件是Activity->ViewGroup->View这样层层传递的,每个层级都应该有处理事件的能力,显然,我们需要两个方法,一个用来传递事件,一个用来处理时事件。我们分别定义三个类:MActivity,MViewGroup,MView 来分别模拟Activity,ViewGroup和View,一步一步实现事件分发。

    • 需求一:将事件能够从Activity传递到最里层View,最里层View能够处理事件。

    • MActivity
    public class MActivity {
    
        private MViewGroup mChild;
      
        public MActivity(MViewGroup child) {
            mChild = child;
        }
    
       //用来传递事件
        protected boolean dispatchTouchEvent(MotionEvent event) {
            mChild.dispatchTouchEvent(event)
        }
    
      //用来处理事件
        protected boolean onTouchEvent(MotionEvent event) {
            return false;
        }
    }
    
    • MViewGroup
    public class MViewGroup extends MView {
    
        private MView mChild;
      
        public MViewGroup(MView child) {
            mChild = child;
        }
    
       //用来传递事件
        protected boolean dispatchTouchEvent(MotionEvent event) {
            return mChild.dispatchTouchEvent(event)
        }
    
      //用来处理事件
        protected boolean onTouchEvent(MotionEvent event) {
            return false;
        }
    }
    
    • MView
    public class  MView {
    
     
       //用来传递事件
        protected boolean dispatchTouchEvent(MotionEvent event) {
            return onTouchEvent(event);
        }
    
      //用来处理事件
        protected boolean onTouchEvent(MotionEvent event) {
            return true;
        }
    }
    
    • 现在看起来,暂时Activity和ViewGroup功能是一样的,因为我们只是想把事件传递到最里层View,并让这个View处理事件,Activity和ViewGroup只需要传递事件。
    • 我们定义了两个方法dispatchTouchEvent(MotionEvent event)和onTouchEvent(MotionEvent event),分别用来传递和处理事件。

    思考

    这个需求很简单,我们只是需要最里层View处理事件,假如,不光最里层View可以处理事件,包裹它的ViewGroup也能处理事件,这应该怎么办呢?比如,ViewGroup是可以点击的。

    • 首先,我们应该让这个事件可以传递到View,因为如果View也是可以点击,需要响应点击事件,这个事件不传给它,它就没法响应了。
    • 再假设一种情况,子View不需要处理事件,假如是类似今日头条这种新闻列表,每个item可能有标题,图片多个子View,但他们不需要处理事件,只要点击item就进入新闻详情页面。也就是说子View有处理和不处理事件两种情况。这时候我们需要根据子View处理事件方法不同的返回值做不同处理。
    • 总结:事件应该先传给View,View不处理,就交给ViewGroup处理,但假如ViewGroup也不处理,那就交给Activity处理,再想一下,假如同一个事件序列的某一个事件,View和ViewGroup都不处理,那么其之后的事件也直接交给Activity就可以,因为View和ViewGroup都不处理,那么之后的事件传给它们也没意义,毕竟事件已经不完整了。
    MActivity
    public class MActivity {
    
        private MViewGroup mChild;
        private boolean isChildHandled=false;
        private boolean isSelfHandled=false;
        public MActivity(MViewGroup child) {
            mChild = child;
        }
        protected boolean dispatchTouchEvent(MotionEvent event) {
            boolean handled=false;
            if(MotionEvent.ACTION_DOWN==event.getAction()){
                clearStatus();
                handled=mChild.dispatchTouchEvent(event);
                if(handled){
                    isChildHandled=true;
                }else {
                    handled=onTouchEvent(event);
                    if(handled){
                        isSelfHandled=true;
                    }
                }
            }else {
                if(isSelfHandled){
                    handled=onTouchEvent(event);
                } else if(isChildHandled){
                    handled = mChild.dispatchTouchEvent(event);
                }
                if(!handled){
                    handled=onTouchEvent(event);
                }
                if(MotionEvent.ACTION_UP==event.getAction()){
                    clearStatus();
                }
    
            }
            return handled;
        }
    
        private void clearStatus() {
            isChildHandled=false;
            isSelfHandled=false;
        }
    
        protected boolean onTouchEvent(MotionEvent event) {
            return true;
        }
        
    }
    
    MViewGroup
    public class MViewGroup extends MView {
    
        private MView mChild;
        private boolean isChildHandled=false;
        private boolean isSelfHandled=false;
        
        public MViewGroup(MView view) {
            mChild=view;
        }
    
    
    
        @Override
        protected boolean dispatchTouchEvent(MotionEvent event) {
    
            boolean handled=false;
            if(MotionEvent.ACTION_DOWN==event.getAction()){
                clearStatus();
                handled=mChild.dispatchTouchEvent(event);
                if(handled){
                    isChildHandled=true;
                }else {
                    handled=onTouchEvent(event);
                    if(handled){
                        isSelfHandled=true;
                    }
                }
            }else {
                if(isSelfHandled){
                    handled=onTouchEvent(event);
                } else if(isChildHandled){
                    handled = mChild.dispatchTouchEvent(event);
                }
                if(MotionEvent.ACTION_UP==event.getAction()){
                    clearStatus();
                }
    
            }
            return handled;
        }
    
        private void clearStatus() {
            isChildHandled=false;
            isSelfHandled=false;
        }
        
    
        @Override
        protected boolean onTouchEvent(MotionEvent event) {
            return true;
        }
    
    }
    
    MView
    public class MView {
    
        protected boolean dispatchTouchEvent(MotionEvent event){
            return onTouchEvent(event);
        }
    
        protected boolean onTouchEvent(MotionEvent event){
            return true;
        }
    }
    

    总结

    • 事件要先传给View,View不处理,后续事件就不传给它了,交给ViewGroup处理,假如ViewGroup也不处理,就交给Activity处理。
    • 很显然,这让我们想到了责任链模式,实际上源码也确实是责任链模式。

    思考

    • 其实我们这么写,会有问题, 假如还是今日头条这个新闻列表,当我们把手放在子View上,子View是个可点击View,这样就会导致子View会响应点击事件,但实际上,我们把手放在子View上,这时候并不能知道是需要响应子View的点击事件还是ViewGroup的滑动事件,需要看接下来的动作才知道,但是按照我们这种写法,子View一看事件来了,就会处理,那列表就永远无法滑动了,那怎么办,其实很明显,单靠DOWN事件,没法判断接下来的动作,得等等看,假如DOWN之后,很多时间内UP,那就响应子View点击事件,假如DOWN之后,一段时间,一直都是MOVE事件,这时候响应ViewGroup的滑动,所以,我们不能单凭DOWN事件,决定事件的处理,我们需要在事件的传递过程中,ViewGroup可以进行干涉,随时可以处理事件,但这样又会有一个新的问题,假如子View在press状态有一个背景高亮的效果,接收到DOWN事件时,子View背景高亮,这时候没问题,但假如ViewGroup拦截了接下来的MOVE事件,那么之后的事件,都不会传递到子View,这就会导致子View背景一直高亮,要解决这个问题,其实也不麻烦,无非就是在ViewGroup拦截的时候,通知一下子View,让子View自己处理一下就可以,那么如何通知子View?肯定不能发送MOVE,UP事件,那就造一个新事件,叫它CANCEL吧,这样看起来就很好了,可是真的很好了吗?想象一下,子View接收到DOWN事件后,开开心心处理着,突然后面的事件都不给它了,就给了它一个CANCEL事件,如果仅仅是涉及到子View UI方面还好,万一子View已经开始和用户交互了,这时候就不太好,所以,子View需要可以告诉爸爸,哪些时候不能拦截自己的事件,这样确实完美了,接下来,就是如何用代码实现了。
    MViewParent 新增的一个接口,子View是否不让ViewGroup拦截事件,单独抽出来逻辑比较清晰。
    public interface MViewParent {
    
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
    
    }
    
    
    MAcitivity 基本和之前一样,只是增加了对CANCEL事件的处理,因为它不需要拦截事件
    public class MActivity {
    
        private MViewGroup mChild;
        private boolean isChildHandled=false;
        private boolean isSelfHandled=false;
        public MActivity(MViewGroup child) {
            mChild = child;
        }
        protected boolean dispatchTouchEvent(MotionEvent event) {
            boolean handled=false;
            if(MotionEvent.ACTION_DOWN==event.getAction()){
                clearStatus();
                handled=mChild.dispatchTouchEvent(event);
                if(handled){
                    isChildHandled=true;
                }else {
                    handled=onTouchEvent(event);
                    if(handled){
                        isSelfHandled=true;
                    }
                }
            }else {
                if(isSelfHandled){
                    handled=onTouchEvent(event);
                } else if(isChildHandled){
                    handled = mChild.dispatchTouchEvent(event);
                }
                if(!handled){
                    handled=onTouchEvent(event);
                }
                //cancel事件也要重置标志位。
                if (ev.actionMasked == MotionEvent.ACTION_UP
                || ev.actionMasked == MotionEvent.ACTION_CANCEL) {
                clearStatus()
            }
    
    
            }
            return handled;
        }
    
        private void clearStatus() {
            isChildHandled=false;
            isSelfHandled=false;
        }
    
        protected boolean onTouchEvent(MotionEvent event) {
            return true;
        }
        
    }
    
    MViewGroup
    public class MViewGroup extends MView implements MViewParent{
    
        private MView mChild;
        private boolean isChildHandled=false;//是否是子View处理事件
        private boolean isSelfHandled=false;//是否是自己处理事件
        private boolean isDisallowIntercept=false;//子View是否不允许拦截事件
    
        public MViewGroup(MView child) {
            mChild=child;
            mChild.mParent=this;
        }
    
    
        @Override
        protected boolean dispatchTouchEvent(MotionEvent event) {
    
            boolean handled=false;
            if(MotionEvent.ACTION_DOWN==event.getAction()){
                clearStatus();
                //新增ViewGroup是否拦截和子View是否允许ViewGroup拦截判断
                if(!isDisallowIntercept&&onInterceptTouchEvent(event)){
                    isSelfHandled=true;
                    handled=onTouchEvent(event);
                }else {
                    handled=mChild.dispatchTouchEvent(event);
                    if(handled){
                        isChildHandled=true;
                    }else {
                        handled=onTouchEvent(event);
                        if(handled){
                            isSelfHandled=true;
                        }
                    }
                }
            }else {
                if(isSelfHandled){
                    handled=onTouchEvent(event);
                } else if(isChildHandled){
                    if(!isDisallowIntercept&&onInterceptTouchEvent(event)) {
                        isSelfHandled=true;
                        MotionEvent cancel=MotionEvent.obtain(event);
                        cancel.setAction(MotionEvent.ACTION_CANCEL);
                        mChild.dispatchTouchEvent(cancel);
                        cancel.recycle();
                    }else {
                        mChild.dispatchTouchEvent(event);
                    }
                }
                if(MotionEvent.ACTION_UP==event.getAction()||MotionEvent.ACTION_UP==event.getAction()){
                    clearStatus();
                }
    
            }
            return handled;
        }
    
        private void clearStatus() {
            isChildHandled=false;
            isSelfHandled=false;
            isDisallowIntercept=false;
        }
    
        protected boolean onInterceptTouchEvent(MotionEvent event){
            return false;
        }
    
    
        @Override
        protected boolean onTouchEvent(MotionEvent event) {
            return true;
        }
    
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept){
            this.isDisallowIntercept=disallowIntercept;
            if(mParent!=null) {
                mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
            }
        }
    
    }
    
    MView
    public class MView {
    
        protected MViewParent mParent;
    
        protected boolean dispatchTouchEvent(MotionEvent event){
            return onTouchEvent(event);
        }
    
        protected boolean onTouchEvent(MotionEvent event){
            return true;
        }
    }
    

    总结

    • 事件分发大体就是这么一个过程,只不过省略了onTouchListener等各种Listener的情况。
    • ViewGroup一旦决定拦截某个事件,那么同一个事件序列后面的事件也应该交给它来处理,而不用再询问是否拦截事件。
    • View处理了DOWN事件,但之后的事件被拦截,需要同时发送个CANCEL事件给View,同时不需要把事件交给ViewGroup自己的onTouchEvent()处理,这样可以保证,一个事件最终只有一个View处理。
    • View和ViewGroup都不处理某事件,Activity会处理这个事件,同一事件序列后面的事件也不会传递给View和ViewGroup,Activity会处理。

    参考

    【透镜系列】看穿 > 触摸事件分发 >

    相关文章

      网友评论

        本文标题:Android——彻底搞懂事件分发机制

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