[例证]从滑动冲突到事件分发(从源码角度分析)

最近在看安卓开发艺术。看到滑动冲突一章,突然有感。mark一下。

先上一个demo效果图:

《[例证]从滑动冲突到事件分发(从源码角度分析)》

上面白色区域是一个listView,外面被一个scrollView包裹着。下面红色区域是一个Linearlayout占位。用来能上下滑动。

布局源码如下:

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. “1.0” encoding=“utf-8”?>  
  2. “http://schemas.android.com/apk/res/android”   
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”>  
  5.   
  6.     
  7.         android:layout_width=“wrap_content”  
  8.         android:layout_height=“match_parent”  
  9.         android:orientation=“vertical”>  
  10.   
  11.         
  12.             android:id=“@+id/list_item”  
  13.             android:layout_width=“match_parent”  
  14.             android:layout_height=“200dp”>  
  15. //此处用来占位,能使整个布局达到上下滑动的条件  
  16.         
  17.             android:layout_width=“match_parent”  
  18.             android:layout_height=“800dp”  
  19.             android:background=“#ff0000”>  
  20.   
  21.           
  22.       
  23.   

《[例证]从滑动冲突到事件分发(从源码角度分析)》

  

  

    
    
     //此处用来占位,能使整个布局达到上下滑动的条件 
     
     
   

  

上述布局样式:会发现,listView无法滑动,上下滑动只能滑动外部的scrollView。

问题一、为什么会滑动冲突?

首先先科普一个知识:

1)事件分发机制,是依照Activity——》ViewGroup——》View,从顶部往下分发。

2)而每个ViewGroup当disallowIntercept为false的时候,都会尝试拦截onInterceptTouchEvent()。(ps:后面我会具体谈disallowIntercept这个参数)

从简上盗一张图:点击打开链接 附上事件分发机制的链接。

《[例证]从滑动冲突到事件分发(从源码角度分析)》

知道这个知识后,思考一下当前demo,

猜想冲突原因:

分发过程中,被上层的ScrollView拦截了。没有分发到ListView。

我们直接去找ViewGroup拦截的方法onInterceptTouchEvent()。

对于当前demo的布局:activity(忽略)不考虑,直接看最外层ViewGroup类:ScrollView。

发现:ScrollView的onInterceptTouchEvent()继承自FrameLayout,而FrameLayout的onInterceptTouchEvent()原封不动的继承自ViewGroup。所以我直接看ViewGroup的onInterceptTouchEvent()方法:

ViewGroup#onInterceptTouchEvent()源码如下:

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  2.         return false;  
  3.     }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

所以ViewGroup默认是不拦截的。而FrameLayout是没有重写这个方法的。再看ScrollView的onInterceptTouchEvent()方法:

ScrollView#onInterceptTouchEvent()源码如下:

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. @Override  
  2.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.         /* 
  4.          * This method JUST determines whether we want to intercept the motion. 
  5.          * If we return true, onMotionEvent will be called and we do the actual 
  6.          * scrolling there. 
  7.          */  
  8.   
  9.         /* 
  10.         * Shortcut the most recurring case: the user is in the dragging 
  11.         * state and he is moving his finger.  We want to intercept this 
  12.         * motion.//笔者注:当action为move操作时,且mIsBeingDragged为真的时候,返回true,拦截 。见注释1   */  
  13.         final int action = ev.getAction();  
  14.         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {  
  15.             return true;  
  16.         }  
  17.   
  18.         /* 
  19.          * Don’t try to intercept touch if we can’t scroll anyway.//笔者注:这个ViewGroup如果不能滑动,则不允许打断。见注释2 
  20.          */  
  21.         if (getScrollY() == 0 && !canScrollVertically(1)) {  
  22.             return false;  
  23.         }  
  24.   
  25.         switch (action & MotionEvent.ACTION_MASK) {  
  26.             case MotionEvent.ACTION_MOVE: {  
  27.                 /* 
  28.                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 
  29.                  * whether the user has moved far enough from his original down touch. 
  30.                  */  
  31.   
  32.                 /* 
  33.                 * Locally do absolute value. mLastMotionY is set to the y value 
  34.                 * of the down event. 
  35.                 */  
  36.                 final int activePointerId = mActivePointerId;  
  37.                 if (activePointerId == INVALID_POINTER) {  
  38.                     // If we don’t have a valid id, the touch down wasn’t on content.  
  39.                     break;  
  40.                 }  
  41.   
  42.                 final int pointerIndex = ev.findPointerIndex(activePointerId);  
  43.                 if (pointerIndex == –1) {  
  44.                     Log.e(TAG, “Invalid pointerId=” + activePointerId  
  45.                             + ” in onInterceptTouchEvent”);  
  46.                     break;  
  47.                 }  
  48.   
  49.                 final int y = (int) ev.getY(pointerIndex);  
  50.                 final int yDiff = Math.abs(y – mLastMotionY);  
  51.                 if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {  
  52.                     mIsBeingDragged = true;//笔者注:见注释3  
  53.                     mLastMotionY = y;  
  54.                     initVelocityTrackerIfNotExists();  
  55.                     mVelocityTracker.addMovement(ev);  
  56.                     mNestedYOffset = 0;  
  57.                     if (mScrollStrictSpan == null) {  
  58.                         mScrollStrictSpan = StrictMode.enterCriticalSpan(“ScrollView-scroll”);  
  59.                     }  
  60.                     final ViewParent parent = getParent();  
  61.                     if (parent != null) {  
  62.                         parent.requestDisallowInterceptTouchEvent(true);  
  63.                     }  
  64.                 }  
  65.                 break;  
  66.             }  
  67.   
  68.             case MotionEvent.ACTION_DOWN: {  
  69.                 final int y = (int) ev.getY();  
  70.                 if (!inChild((int) ev.getX(), (int) y)) {  
  71.                     mIsBeingDragged = false;  
  72.                     recycleVelocityTracker();  
  73.                     break;  
  74.                 }  
  75.   
  76.                 /* 
  77.                  * Remember location of down touch. 
  78.                  * ACTION_DOWN always refers to pointer index 0. 
  79.                  */  
  80.                 mLastMotionY = y;  
  81.                 mActivePointerId = ev.getPointerId(0);  
  82.   
  83.                 initOrResetVelocityTracker();  
  84.                 mVelocityTracker.addMovement(ev);  
  85.                 /* 
  86.                 * If being flinged and user touches the screen, initiate drag; 
  87.                 * otherwise don’t.  mScroller.isFinished should be false when 
  88.                 * being flinged. 
  89.                 */  
  90.                 mIsBeingDragged = !mScroller.isFinished();  
  91.                 if (mIsBeingDragged && mScrollStrictSpan == null) {  
  92.                     mScrollStrictSpan = StrictMode.enterCriticalSpan(“ScrollView-scroll”);  
  93.                 }  
  94.                 startNestedScroll(SCROLL_AXIS_VERTICAL);  
  95.                 break;  
  96.             }  
  97.   
  98.             case MotionEvent.ACTION_CANCEL:  
  99.             case MotionEvent.ACTION_UP:  
  100.                 /* Release the drag */  
  101.                 mIsBeingDragged = false;  
  102.                 mActivePointerId = INVALID_POINTER;  
  103.                 recycleVelocityTracker();  
  104.                 if (mScroller.springBack(mScrollX, mScrollY, 000, getScrollRange())) {  
  105.                     postInvalidateOnAnimation();  
  106.                 }  
  107.                 stopNestedScroll();  
  108.                 break;  
  109.             case MotionEvent.ACTION_POINTER_UP:  
  110.                 onSecondaryPointerUp(ev);  
  111.                 break;  
  112.         }  
  113.   
  114.         /* 
  115.         * The only time we want to intercept motion events is if we are in the 
  116.         * drag mode. 
  117.         */  
  118.         return mIsBeingDragged;  
  119.     }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.//笔者注:当action为move操作时,且mIsBeingDragged为真的时候,返回true,拦截 。见注释1   */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;
        }

        /*
         * Don't try to intercept touch if we can't scroll anyway.//笔者注:这个ViewGroup如果不能滑动,则不允许打断。见注释2
         */
        if (getScrollY() == 0 && !canScrollVertically(1)) {
            return false;
        }

        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
                    break;
                }

                final int pointerIndex = ev.findPointerIndex(activePointerId);
                if (pointerIndex == -1) {
                    Log.e(TAG, "Invalid pointerId=" + activePointerId
                            + " in onInterceptTouchEvent");
                    break;
                }

                final int y = (int) ev.getY(pointerIndex);
                final int yDiff = Math.abs(y - mLastMotionY);
                if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
                    mIsBeingDragged = true;//笔者注:见注释3
                    mLastMotionY = y;
                    initVelocityTrackerIfNotExists();
                    mVelocityTracker.addMovement(ev);
                    mNestedYOffset = 0;
                    if (mScrollStrictSpan == null) {
                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                    }
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            }

            case MotionEvent.ACTION_DOWN: {
                final int y = (int) ev.getY();
                if (!inChild((int) ev.getX(), (int) y)) {
                    mIsBeingDragged = false;
                    recycleVelocityTracker();
                    break;
                }

                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionY = y;
                mActivePointerId = ev.getPointerId(0);

                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                /*
                * If being flinged and user touches the screen, initiate drag;
                * otherwise don't.  mScroller.isFinished should be false when
                * being flinged.
                */
                mIsBeingDragged = !mScroller.isFinished();
                if (mIsBeingDragged && mScrollStrictSpan == null) {
                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                }
                startNestedScroll(SCROLL_AXIS_VERTICAL);
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                /* Release the drag */
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                recycleVelocityTracker();
                if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
                    postInvalidateOnAnimation();
                }
                stopNestedScroll();
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
        }

        /*
        * The only time we want to intercept motion events is if we are in the
        * drag mode.
        */
        return mIsBeingDragged;
    }

注释1:mIsBeingDragged这个参数,在ScrollView中定义如下:

ScrollView#mIsBeingDragged源码如下:

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. /** 
  2.      * True if the user is currently dragging this ScrollView around. This is 
  3.      * not the same as ‘is being flinged’, which can be checked by 
  4.      * mScroller.isFinished() (flinging begins when the user lifts his finger). 
  5.      */  
  6.     private boolean mIsBeingDragged = false;  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

/**
     * True if the user is currently dragging this ScrollView around. This is
     * not the same as 'is being flinged', which can be checked by
     * mScroller.isFinished() (flinging begins when the user lifts his finger).
     */
    private boolean mIsBeingDragged = false;

mIsBeingDragged默认为false,目前此处是未拦截的。

注释2:对上面的说法,先看一个效果图:

《[例证]从滑动冲突到事件分发(从源码角度分析)》

上图的红色占位区域位10dp,则scrollView无论如何都不会滑动,因此。因此并未体现滑动冲突效果onInterceptTouchEvent()返回的是false。

注释3:当滑动距离大于最小滑动距离时,onInterceptTouchEvent()返回true。我对此验证重写了ScrollView方法。

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. public class MyScrollView extends ScrollView{  
  2.     public MyScrollView(Context context, AttributeSet attrs) {  
  3.         super(context, attrs);  
  4.     }  
  5.   
  6.     @Override  
  7.     public boolean onTouchEvent(MotionEvent ev) {  
  8.         Log.e(“MyScrollView”,super.onTouchEvent(ev)+“||onTouchEvent”);  
  9.         return super.onTouchEvent(ev);  
  10.     }  
  11.   
  12.     @Override  
  13.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  14.         Log.e(“MyScrollView”,super.dispatchTouchEvent(ev)+“||dispatchTouchEvent”);  
  15.         return super.dispatchTouchEvent(ev);  
  16.     }  
  17.   
  18.     @Override  
  19.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  20.         Log.e(“MyScrollView”,super.onInterceptTouchEvent(ev)+“||onInterceptTouchEvent”);  
  21.         return super.onInterceptTouchEvent(ev);  
  22.     }  
  23. }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

public class MyScrollView extends ScrollView{
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e("MyScrollView",super.onTouchEvent(ev)+"||onTouchEvent");
        return super.onTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("MyScrollView",super.dispatchTouchEvent(ev)+"||dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("MyScrollView",super.onInterceptTouchEvent(ev)+"||onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }
}

点击listView区域,对onInterceptTouchEvent打印Log如下:

《[例证]从滑动冲突到事件分发(从源码角度分析)》

说明:

前面多个false原因滑动距离不够。当滑动距离大于最小距离后,onInterceptTouchEvent()返回true。注:每一次滑动,都以滑动距离达到最小滑动判断是否滑动。一次滑动包含多个小的滑动。以action down 到 action up ,判断滑动距离。

总结:

滑动冲突产生原因:外部ScrollView拦截了事件,并消费了事件。

解决方案:

处理滑动冲突的方法包涵两种:内部拦截法和外部拦截法:

1)内部拦截法代码:

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. @Override  
  2.    public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.        switch (ev.getAction()){  
  4.            case MotionEvent.ACTION_DOWN:  
  5.                getParent().requestDisallowInterceptTouchEvent(true);  
  6.                break;  
  7.            case MotionEvent.ACTION_MOVE:  
  8.                getParent().requestDisallowInterceptTouchEvent(true);  
  9.                break;  
  10.            case MotionEvent.ACTION_UP:  
  11.                getParent().requestDisallowInterceptTouchEvent(true);  
  12.                break;  
  13.            default:  
  14.                getParent().requestDisallowInterceptTouchEvent(true);  
  15.                break;  
  16.        }  
  17.        return super.dispatchTouchEvent(ev);  
  18.    }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_UP:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            default:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

通过以上方法,发现滑动冲突问题不存在了。实现了,滑动listView则滑动listView自身。

外部红色区域滑动,则滑动外部的ScrollView区域。

内部拦截法的原理

此处可能会有一个疑问:我们这么做的原理是什么?

既然只有一个方法requestDisallowInterceptTouchEvent():

ScrollView#requestDisallowInterceptTouchEvent源码如下:

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. @Override  
  2.     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {  
  3.         if (disallowIntercept) {  
  4.             recycleVelocityTracker();  
  5.         }  
  6.         super.requestDisallowInterceptTouchEvent(disallowIntercept);  
  7.     }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

@Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (disallowIntercept) {
            recycleVelocityTracker();
        }
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

disallowIntercept为true则,上面的代码暂时忽略,直接使用父类的的requestDisallowInterceptTouchEvent:

ViewGroup#requestDisallowInterceptTouchEvent源码如下:(代码二)

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {  
  2.   
  3.         if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {  
  4.             // We’re already in this state, assume our ancestors are too  
  5.             return;  
  6.         }  
  7.   
  8.         if (disallowIntercept) {  
  9.             mGroupFlags |= FLAG_DISALLOW_INTERCEPT;  
  10.         } else {  
  11.             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  12.         }  
  13.   
  14.         // Pass it up to our parent  
  15.         if (mParent != null) {  
  16.             mParent.requestDisallowInterceptTouchEvent(disallowIntercept);  
  17.         }  
  18.     }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

Viewparent#requestDisallowInterceptTouchEvent

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);

首先放下位运算,先看看这个disallowIntercept这个标识。发现很眼熟。本文最开始也提到过。

对,就在这。ViewGroup#dispatchTouchEvent()源码:(代码一)

[java]
view plain
copy
print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. “code”   class = “java” > @Override   
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.         //……..省略…..  
  4.     final boolean intercepted;  
  5.  if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)  
  6.     { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  7.  if (!disallowIntercept) {   
  8.     intercepted = onInterceptTouchEvent(ev);   
  9.     ev.setAction(action);  
  10.     // restore action in case it was changed }   
  11.     else { intercepted = false; } }   
  12.     else {   
  13.     // There are no touch targets and this action is not an initial down  
  14.     // so this view group continues to intercept touches. intercepted = true; }  
  15.     //……..省略…..  
  16.     return handled;  
  17.     }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //........省略.....
	final boolean intercepted;
 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)
 	{ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 if (!disallowIntercept) { 
	intercepted = onInterceptTouchEvent(ev); 
	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. intercepted = true; }
   	//........省略.....
	return handled;
    }

这个disallowIntercept标识也是一个位运算得到的。

disallowIntercept的位运算

好吧,这个位运算绕不开了。

1、先看mGroupFlags,mGroupFlags初始值为0。FLAG_DISALLOW_INTERCEPT初始值为

[java]
view plain
copy

print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. /** 
  2.      * When set, this ViewGroup should not intercept touch events. 
  3.      * {@hide} 
  4.      */  
  5.     protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

/**
     * When set, this ViewGroup should not intercept touch events.
     * {@hide}
     */
    protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000

2、mGroupFlags & FLAG_DISALLOW_INTERCEPT 计算知道值为0。

也就是:0000 0000 0000 0000 0000 & 1000 0000 0000 0000 0000 =0000 0000 0000 0000 0000;

代码一的:final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 

相当于final boolean disallowIntercept = (0) != 0; 

即disallowIntercept =false;

3、再看requestDisallowInterceptTouchEvent(true)时做了什么:

从代码二能看到true时对mGroupFlags重新赋值:mGroupFlags |= FLAG_DISALLOW_INTERCEPT;

也就是mGroupFlags = mGroupFlags| FLAG_DISALLOW_INTERCEPT;

同理:mGroupFlags =0000 0000 0000 0000 0000 |1000 0000 0000 0000 0000 = 1000 0000 0000 0000 0000 ;

然后再看代码一:final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 

同理:final boolean disallowIntercept = (1000 0000 0000 0000 0000 & 1000 0000 0000 0000 0000)!= 0;

因此:disallowIntercept = true;

值我们都知道了,再回到源码 代码一:

[java]
view plain
copy

print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. if (!disallowIntercept) {  
  2.                     intercepted = onInterceptTouchEvent(ev);  
  3.                     ev.setAction(action); // restore action in case it was changed  
  4.                 } else {  
  5.                     intercepted = false;  
  6.                 }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }

总结:

默认情况下:disallowIntercept = false,执行intercepted = onInterceptTouchEvent(ev); 也就是执行我们的onInterceptTouchEvent拦截方法。

当解决滑动冲突时,getParent().requestDisallowInterceptTouchEvent(true),disallowIntercept = false,则始终不执行onInterceptTouchEvent拦截方法。

2)外部拦截法代码:

(此段代码非针对该demo)

[java]
view plain
copy

print
?
《[例证]从滑动冲突到事件分发(从源码角度分析)》
《[例证]从滑动冲突到事件分发(从源码角度分析)》

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  2.         boolean intercepted = false;  
  3.         int x = (int) ev.getX();  
  4.         int y = (int) ev.getY();  
  5.   
  6.         switch (ev.getAction()) {  
  7.             case MotionEvent.ACTION_DOWN:  
  8.                 intercepted = false;  
  9.                 break;  
  10.             case MotionEvent.ACTION_MOVE: {  
  11.                 int deltaX = x - mLastXIntercept;  
  12.                 int deltaY = y - mLastYIntercept;  
  13.                 if (Math.abs(deltaX) > Math.abs(deltaY)) {  
  14.                     intercepted = true;  
  15.                 } else {  
  16.                     intercepted = false;  
  17.                 }  
  18.                 break;  
  19.             }  
  20.             case MotionEvent.ACTION_UP: {  
  21.                 intercepted = false;  
  22.                 break;  
  23.             }  
  24.         }  
  25.         mLastXIntercept = x;  
  26.         mLastYIntercept = y;  
  27.         return intercepted;  
  28.     }  

《[例证]从滑动冲突到事件分发(从源码角度分析)》

public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;
                break;
            }
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }

总结:

从上述代码能看出外部拦截法,是重写了onInterceptTouchEvent()方法。通过滑动操作的不同,返回true(拦截) 或者 false(不拦截)。

针对不同滑动操作,来解决滑动冲突的情景,使用外部拦截法。

外部拦截法的原理:

同事件分发机制的原理,ViewGroup布局的dispatchtouchevent()方法,默认使用onInterceptTouchEvent()方法(默认值为false,不拦截)判断是否拦截,若返回值为true,则ViewGroup自身消费事件,走自身的onTouchevent事件(见上叙述的事件分发机制的图片)。当出现滑动冲突的时候可通过dispatchtouchevent()返回值,来判断是否需要拦截。




    原文作者:Android源码分析
    原文地址: https://juejin.im/entry/58217dc8570c350060bc5796
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞