android滑动触摸事件

参考博客:即于下面博客演变出自己的理解

     https://juejin.im/post/5c3c8538f265da6142741d63

      https://blog.csdn.net/xmxkf/article/details/79481243

郭霖

    https://blog.csdn.net/guolin_blog/article/details/9097463

   https://blog.csdn.net/guolin_blog/article/details/9153747

   事件分发机制:整体上遵循递归原则,递是指事件分发传递从Activity---->DecorView ----->ViewGroup---->View

  归是指:事件的消费从 View---->ViewGroup---->DecorView---->Activity

  要素,连接关系,运行环境如下图

 

分析 :

 1: 方法要素:dispatchTouchEvent是在View中定义的,ViewGroup重写了它。并且View 中的dispatchTouchEvent函数是交给

        OnTouchListener处理的,并调用onTouchEvent ----->这是都是View类型控件自己处理

       ViewGroup类控件,先是判断是否拦截或者是否禁止拦截这两个变量,来决定是否交给子控件处理,然后才自己处理,自己处理的逻辑和View处理的 逻辑是一样的

2:  要素说明 :

    onInterceptTouchEvent(MotionEvent ev) ----》如果返回true,那么子类就没有机会获的事件

   onTouchListener.onTouch ----->监听touch事件,onTouch返回值决定是否消费此事件,是否回调onTouchEvent

  onTouchEvent ------>控件解析处理事件

  setOnClickListener ----->View中的函数,设置点击事件回调onClick函数

3: 关键函数,参数解析

     disallowIntercept /  requestDisallowIntercept 变量,是否请求父控件禁止拦截事件,默认是false ---->即 默认是拦截的

     interceptTouchEvent() -----> 1: 一般在滑动冲突的时候,我们复写这个函数,然后在函数里面加上一个判断条件,什么条件下返回true 拦截事件,什么情况下返回false 不拦截事件。2:系统对于这个函数的处理事:判断 用户的触摸事件是在父控件下的子控件范围内,如果在就返回false 否则返回true ,那要是不在子控件范围内(用户就是点击 activity空白处)或者子控件没有消费事件(dispatchTouchEvent() 返回false )那么interceptTouchEvent()还是返回false, 继续执行super.dispatchTouchEvent 但是这个已经是ViewGroup自己的事件执行了

    

4: View对 dispatchTouchEvent函数的调用 处理关系如果返回true (不管是子类返回还是回调父控件返回),事件都是在这一层结束

       1: 判断控件enable是否可用 && 是否设置onTouchListener监听 调用onTouch函数

     2: onTouch函数:如果返回 false ,说明没有被消费,继续调用 onTouchEvent, 返回true 直接给dispatchTouchEvent返回true

     事件在View这一层消费

    

     5:View对onTouchEvent函数分析

      该方法会处理一系列的 action_down  action_move  action_up 事件(按下,滑动,抬起),并在 up事件的时候会回调 performClick() 函数,并且回调OnClickListener.onClick()方法 

伪代码分析:

       1: ViewGroup 的 dispatchTouchEvent函数 

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                    child.getHitRect(frame);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }
        return super.dispatchTouchEvent(ev);
    }
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
        }
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        mMotionTarget = null;
    }
    return target.dispatchTouchEvent(ev);

}

2: View的 dispatchTouchEvent函数 

总结  

   1: android触摸事件是先从 ViewGroup 在传递给View

   2:  ViewGroup 的dispatchTouchEvent 函数 会 根据 disallowIntercept 来决定是否分发给子控件,此变量的作用就是在 interceptTouchEvent 返回true 之后(不允许事件向子类传递)子类还是想收到 事件这个问题。

   3: 子类如果消费事件了,就会通过 dispatchTouchEvent函数传递消费事件的返回值true

  4:  View 的dispatchTouchEvent  会判断 控件是否设置 enable  && onTouchListener 来决定是否走onTouch函数,onTouch函数的返回false, 又会走 onTouchEvent 函数。最终将事件的消费情况传递给 父类

应用:

   说了这么都,实际的应用了 ?

  1: 一般如果自定义控件继承ViewGroup ,那么需要重写 onInterceptTouchEvent() 函数,并根据自己的的业务逻辑灵活的返回true 或者false  -------》比如在ViewPager 中嵌套 ListView 或者RecyclerView 就要控制好 ViewPager 的 onInterceptTouchEvent()函数是否拦截事件了 ,一般都是定义一个 规则,超过某段距离认为用户想横向滑动,这个时候就拦截事件不向RecyclerView 分发,没超过一定距离就 认为用户想垂直滑动,这个时候事件件不拦截事件需要向RecyclerView 分发

 

猜你喜欢

转载自blog.csdn.net/u013620306/article/details/107471236