Android进阶学习(8)-- View事件分发、滑动冲突

View事件分发、滑动冲突

  • view事件分发
        • 事件分发、拦截、消费
        • 单击事件源码分析
  • 滑动冲突
        • View事件分发整体流程
        • 事件冲突源码分析
        • 事件冲突解决办法

view事件分发

事件分发、拦截、消费

在我们的代码中,事件是由 dispatchTouchEvent 负责分发,由 onInterceptTouchEvent 决定是否拦截,最后由 onTouchEvent 消费;
Android进阶学习(8)-- View事件分发、滑动冲突_第1张图片

单击事件源码分析

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });

这是设置点击事件的方法,那么setOnClickListener究竟是如何生效的?点进setOnClickListener源码:
Android进阶学习(8)-- View事件分发、滑动冲突_第2张图片
通过源码发现只是将我们的处理逻辑传给了ListenerInfo中,正真的调用是在 View 中的 dispatchTouchEvent中实现的:
Android进阶学习(8)-- View事件分发、滑动冲突_第3张图片
Android进阶学习(8)-- View事件分发、滑动冲突_第4张图片
我们注意上面圈起来的 if 判断:
Android进阶学习(8)-- View事件分发、滑动冲突_第5张图片
如果我们只设置了setOnClickListener,那么很明显进不去这个 if ,接着看下面的代码:
Android进阶学习(8)-- View事件分发、滑动冲突_第6张图片
result 默认为false,中间是 && 所以会执行后面的 onTouchEvent:
Android进阶学习(8)-- View事件分发、滑动冲突_第7张图片
onTouchEvent也就是上面说的事件消费者,我们的触摸事件分为以下几个:
Android进阶学习(8)-- View事件分发、滑动冲突_第8张图片
点击事件的触摸流程是DOWN,MOVE,UP,直到UP之后才算完成点击,所以点击事件的调用肯定在ACTION_UP中:
Android进阶学习(8)-- View事件分发、滑动冲突_第9张图片
我们往下看会看到这样的一句代码:
Android进阶学习(8)-- View事件分发、滑动冲突_第10张图片
Android进阶学习(8)-- View事件分发、滑动冲突_第11张图片
Android进阶学习(8)-- View事件分发、滑动冲突_第12张图片
在setOnClickListener的源码中,我们就是把单击事件逻辑赋值给了ListenerInfo中的mOnClickListener,在performClick中 进行了调用;
这就完成了一次单击事件

滑动冲突

View事件分发整体流程

Android进阶学习(8)-- View事件分发、滑动冲突_第13张图片

事件冲突源码分析

根据上面的流程图,我们从Activity开始一层层分析源码找到事件冲突的根源,首先是Activity中的dispatchEvent:
Android进阶学习(8)-- View事件分发、滑动冲突_第14张图片
Activity中通过getWindow 调用了 superDispatchTouchEvent ,getWindow 是获得了Window的对象,Window的实现类只有一个也就是 PhoneWindow,由Window的源码中的注释我们也能得到这一点;
Android进阶学习(8)-- View事件分发、滑动冲突_第15张图片
接着看PhoneWindow的源码:
Android进阶学习(8)-- View事件分发、滑动冲突_第16张图片
在PhoneWindow的superDispatchTouchEvent中调用了 mDecor的superDispatchTrackballEvent,这个mDecor也就是DecorView
在这里插入图片描述
接着看DecorView中的superDispatchTouchEvent方法:
Android进阶学习(8)-- View事件分发、滑动冲突_第17张图片
接着点进去dispatchTouchEvent:
Android进阶学习(8)-- View事件分发、滑动冲突_第18张图片
跳到了ViewGroup中的dispatchTouchEvent,先是进行了一系列检查的代码,这些不重要,接着往下看代码:
Android进阶学习(8)-- View事件分发、滑动冲突_第19张图片
我们触摸事件肯定会先触发DOWN,我们假设现在的DOWN,进来这个if之后会清除一些target 和 state,接着往下走:
Android进阶学习(8)-- View事件分发、滑动冲突_第20张图片
到这里,就和onInterceptTouchEvent连接上了,onInterceptTouchEvent也就是 事件拦截者,接着往下走:
Android进阶学习(8)-- View事件分发、滑动冲突_第21张图片
Android进阶学习(8)-- View事件分发、滑动冲突_第22张图片
注意这个if, newTouchTarget在初始化的时候是null,后面的childrenCount 是当前viewGroup中的子View数量,所以可以进去这个 if 判断:
在这里插入图片描述
在这里插入图片描述
我们看一下buildTouchDispatchChildList 这个方法:
Android进阶学习(8)-- View事件分发、滑动冲突_第23张图片
Android进阶学习(8)-- View事件分发、滑动冲突_第24张图片
这个方法主要是对view进行了排序,这里由一个 getZ 方法,下面画图解释一下:
假设现在一个Activity的布局是这样的,x轴 y轴都没有问题
Android进阶学习(8)-- View事件分发、滑动冲突_第25张图片
Z轴实际上是这样的:
Android进阶学习(8)-- View事件分发、滑动冲突_第26张图片
所以,根据源码中的逻辑,排序后的数组应该是这样的:
Android进阶学习(8)-- View事件分发、滑动冲突_第27张图片
我们继续回到ViewGroup中的dispatchTouchEvent方法中:
Android进阶学习(8)-- View事件分发、滑动冲突_第28张图片
拿到这个存放view的数组后开始循环,循环的第一个,应该是在最上层的子view
,拿到这个view之后 接着往下走会看到这样的代码:
Android进阶学习(8)-- View事件分发、滑动冲突_第29张图片
canReceivePointerEvents 主要判断view 是否动画 是否可见
isTransformedTouchPointInView 判断触摸的区域是否是当前view
如果判断不通过,则 continue 继续拿下一个view,如果通过,则不走if 继续往下走:
Android进阶学习(8)-- View事件分发、滑动冲突_第30张图片
Android进阶学习(8)-- View事件分发、滑动冲突_第31张图片
这里可以看到 返回的target一般是不为null的:
Android进阶学习(8)-- View事件分发、滑动冲突_第32张图片
到这里,循环就结束了,接着往下看:
Android进阶学习(8)-- View事件分发、滑动冲突_第33张图片
这里有调到了dispatchTransformedTouchEvent方法,并且cancel是false,接着看dispatchTransformedTouchEvent方法:
Android进阶学习(8)-- View事件分发、滑动冲突_第34张图片
传过来的child不为空则,调用child 也就是 子view的dispatchTouchEvent:
Android进阶学习(8)-- View事件分发、滑动冲突_第35张图片
到这里为止,就和前面分析的单击事件源码流程对上了。
那么也就是说,布局中的一层层view,是从最上层的view开始处理事件,当前view不处理的话,再抛给它的父view,以此循环。

事件冲突解决办法

由上面的分析,我们只需要指定 view 或者 它的父view 是否需要拦截事件即可,以下场景为例,ScrollerView的中间嵌套一个ScrollerView:
Android进阶学习(8)-- View事件分发、滑动冲突_第36张图片
那么它冲突的原因就是子View(也就是里面嵌套的ScrollerView)的事件被父View(最外层的ScrollerView)消费了;
那么我们可以根据条件修改子View拦截事件的时机,代码如下:

public class MyScrollView extends ScrollView {

    float mLastX = 0;
    float mLastY = 0;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        float x = ev.getX();
        float y = ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastX = ev.getX();
                mLastY = ev.getY();
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = x - mLastX;
                float dy = y - mLastY;
                Log.e("dx ->", dx +"");
                Log.e("dy ->", dy +"");
                if(dy < 0){//向下滑
                    //滑动到底部 停止拦截事件
                    if (getHeight() + getScrollY() == getChildAt(0).getHeight()) {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }else if(dy > 0){//向上滑
                    //滑动到顶部 停止拦截事件
                    if (getScrollY() == 0){
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }else {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        return super.dispatchTouchEvent(ev);
    }
}

以上代码只是简单的处理了冲突,子view 可以响应自己的事件,实际工作中的开发,需要根据业务逻辑,在各种条件中判断子View何时需要拦截事件。

你可能感兴趣的:(Android,进阶学习)