当前位置:网站首页>根据热门面试题分析Android事件分发机制(二)---事件冲突分析处理

根据热门面试题分析Android事件分发机制(二)---事件冲突分析处理

2022-07-07 07:03:00 光阴剑客

(一)事件冲突概述

事件冲突一般发生在父view和子view的组合中,比如说viewpager和listview 的组合中。产生事件冲突的原因是一个事件(down,up,move)到来时,有时候我们希望是A view处理,但是却是B view处理。这样就导致了我们想处理事件的view收不到事件,不想处理事件的view收到了事件。而解决冲突就是我们通过viewGroup的onInterceptTouchEvent()方法去合理的分发事件,让想处理事件的view收到事件,不想处理的view躺平啥也不做。

(二)没有事件冲突的效果展示

在这里插入图片描述(看看美女,再写代码~~)
言归正传,如上图所示,demo是由一个viewpager+listView的组合,我们发现在没有事件冲突的情况下,应该是既能左右滑动,也能上下滑动。为了演示,我们分别继承自viewpager和Listview,viewPager作为父view,重写它的onInterceptTouchEvent方法,listview作为子view,重写dispatchTouchevent.(需要的时候),demo的源码很简单,我会在文章末尾给出。
(说明:如果不重写onInterceptTouchEvent和listview的dispatchTouchevent方法,我们直接使用viewpager+listview组合会发现不会产生事件冲突,那是因为sdk里面已经解决了冲突,可以查看viewpager的源码)

模拟冲突的产生
情景1: 当我们在父view,也就是viewPager的onInterceptTouchEvent方法中直接返回true,拦截事件,这时候我们会发现我们只可以响应view pager的左右滑动,而不能响应listview的上下滑动,原因就是我们在父view直接拦截了事件,导致子view listview 收不到事件,无法响应事件。
情景2: 当我们在父view,也就是viewPager的onInterceptTouchEvent方法中直接返回false,不拦截事件,我们这时会发现,viewpager 不可以左右滑动,只可以上下滑动listview,这是因为viewpager不拦截事件,将事件分发给了子view处理,所以他无法响应自己的左右滑动事件。

(三)面试题:你遇到过滑动冲突吗?你是怎么处理的?

解析: 这时假如你自己遇到过,就答你遇到的冲突产生的情景,然后说下你的解决方法,如果你没有遇到过,就回答咱们本文中提到的viewpager+listview的场景。根据上面的内容总结下就可以,然后就是如何解决冲突的回答,这时候你可以回答有两种冲突解决方法:
方法一:在父view的onInterceptTouchEvent中解决冲突,代码如下:

   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    
        //在父view中进行冲突处理,不需要子view实现dispatchTouchEvent方法
        float x = ev.getX();//记录下x,y的初始值
        float y = ev.getY();
        switch (ev.getAction()){
    
            case MotionEvent.ACTION_DOWN:      
                super.onInterceptTouchEvent(ev);
                //这里需要调用sdk中的viewpager中的
                //onInterceptTouchEvent方法处理一些特殊问题
                //不调用的话我们拦截了事件也无法响应滑动
                return false;//不能拦截down事件,拦截了down
                //事件,就无法收到后面的move,和up事件了
            case MotionEvent.ACTION_MOVE:
                float xDiff = Math.abs(x-mLastX);
                //当move事件来的时候我们去计算本次的x值和上次的
                //x值,得到一个差值
                float yDiff = Math.abs(y-mLastY);
                //y值的计算和x一样

                if(xDiff > yDiff){
    //x的差值大于y的差值,表示当前是左右滑动。此时应该把事件交给viewpager处理,拦截事件
                    isIntercept = true;
                    break;
                }

                if(yDiff > xDiff){
    //y值大于x值,表示当前是上下滑动,此时应该把事件交给listview处理,不拦截事件
                    isIntercept = false;
                    break;
                }
              // break;

        }
      
        mLastX = x;//更新上一次的x值和y值
        mLastY = y;

        return isIntercept;
    }

方法2:在子view中做滑动冲突处理,listview中的代码如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
        float x = ev.getX();
        float y = ev.getY();
        switch (ev.getAction()){
    
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                //请求父view不拦截事件
                break;
            case MotionEvent.ACTION_MOVE:
                float xDiff = Math.abs(x-mLastX);
                float yDiff = Math.abs(y-mLastY);

                if(xDiff > yDiff){
    
                //当前是左右滑动,自己不需要处理事件,请求父view拦截事件。
                    getParent().requestDisallowInterceptTouchEvent(false);
                }

                break;
            default:break;

        }

        mLastX = x;
        mLastY = y;
        //调用父类的dispatchTouchEvent处理事件
        return super.dispatchTouchEvent(ev);
    }

viewPager中的代码如下:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    
        //在子view中进行冲突处理.
        if(ev.getAction() == MotionEvent.ACTION_DOWN){
    
            super.onInterceptTouchEvent(ev);
            //必须返回false,否则子view无法收到事件,此处会有一个面试题
            return false;
        }
        //返回true,是为了当子view请求父view拦截事件的时候,好拦截事件
        return true;
 }

在子view中处理冲突和在父view中处理冲突的原理差不多,都是通过拦截和不拦截的方式将事件分给需要的view。回答的时候就把两种方法大致描述一下。
然后接着,面试官可能会问你:为啥不能拦截down事件,拦截了down事件,后面的move,up事件还能收到吗?
这两个问题其实互为因果,也就是拦截了down事件,后面的move,up事件都无法收到了。这时候你就可以开始将你看的事件分发机制的源码摆出来了:

...省略了部分代码
// Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
    
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //如果子view没有调用requestDisallowInterceptTouchEvent方法设置标志的话,这个地方会为false,因为在前面down事件来的时候会重置一下标志
                if (!disallowIntercept) {
    
                    intercepted = onInterceptTouchEvent(ev);//若是拦截了down事件,这个地方会返回true;无法进入到接下来的事件分发逻辑,然后mFirstTouchTarget就不会被赋值,无法走入到当前的代码逻辑中,导致后面来的move和up事件直接会被拦截
               =============插播片段==============
                     if (!canceled && !intercepted) {
    
                     ....
                    // 事件分发处理逻辑
                    .....
                     }
               ===========================
                    ev.setAction(action); // restore action in case it was changed
                } else {
    
                    intercepted = false;
                }
            } else {
    
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                //如果没有处理down事件的时候,move和up事件都会被拦截
                intercepted = true;
            }
 ...省略了部分代码

具体的事件分发流程,可以看我上一篇博客根据热门面试题分析Android事件分发机制(一)

(四)总结:

其实事件分发机制和滑动冲突只要理解了源码都不难解决,我的建议就是要看博客然后自己去撸源码,写demo模拟每一种场景,这时你就会发现这些原理都是通的,在面试的时候也能帮你拿到主动权,大多数时候我们面试的时候感觉自己都被面试官气势给压住,其实都是我们技术了解得不深的原因。当我们技术了解得够透彻的时候,你去面试的时候会发现,面试官会被你虐的。那种感觉特别爽

(五)demo地址

这个源码我是放到gitee上的,已经申请了开放权限,还在审批,如果急须源码的伙伴,可以留个邮箱,我私发给你们~~~~
事件冲突演示demo地址

原网站

版权声明
本文为[光阴剑客]所创,转载请带上原文链接,感谢
https://blog.csdn.net/zxj2589/article/details/125579732