CoordinatorLayout、Behavior和嵌套滑动(

2017-09-18  本文已影响171人  HoooChan

嵌套滑动

一个CoordinatorLayout嵌套NestedScrollView :点击之后弹起

首先调用CoordinatorLayout的onInterceptTouchEvent()函数,返回false。之后调用NestedScrollView的onInterceptTouchEvent()。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
            
            ......

            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 = MotionEventCompat.getPointerId(ev, 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. We need to call computeScrollOffset() first so that
                 * isFinished() is correct.
                */
                mScroller.computeScrollOffset();
                mIsBeingDragged = !mScroller.isFinished();
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            }

            ......
        }

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

这里先看CoordinatorLayout对 ACTION_DOWN的处理。首先判断该触摸点是否在NestedScrollView的范围内,如果在,则继续。这里用到VelocityTracker这个类,主要用于通过跟踪一连串事件实时计算出当前的速度。在这次点击中NestedScrollView并没有在滚动,所以mScroller.isFinished()是返回true的,所以mIsBeingDragged为false,此次动作没有拦截。在返回之前调用了startNestedScroll()这个方法。

    @Override
    public boolean startNestedScroll(int axes) {
        return mChildHelper.startNestedScroll(axes);
    } 

可以看到在这里调用了NestedScrollingChildHelper​的startNestedScroll()方法。

    public boolean startNestedScroll(int axes) {
        if (hasNestedScrollingParent()) {
            // Already in progress
            return true;
        }
        // 寻找是否有可以进行嵌套滑动的父布局
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                    mNestedScrollingParent = p;
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    } 

最开始先判断是否有可以进行嵌套滑动的父布局,只要正在进行嵌套滑动,这个函数就会返回true,但一开始是返回false的。之后有一个循环,寻找是否有可以处理嵌套滑动的父布局。在循环过程中,child一直是parent的直接子View,而且child一直包含mView。

public boolean hasNestedScrollingParent() {
        return mNestedScrollingParent != null;
    } 

之后调用ViewParentCompat.onStartNestedScroll(p, child, mView, axes)。如果返回true,则该可滚动的视图有可嵌套滑动的父布局。

    public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes) {
        return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
    }​ 

这里的target是触发嵌套滑动的可滚动的View,chid是Parent的直接子View,它包含target。这里我们NestedScrollView直接就是CoordinatorLayout的子View,所以child和target是同一个。IMPL用来处理兼容性。

    static final ViewParentCompatImpl IMPL;
    static {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 21) {
            IMPL = new ViewParentCompatLollipopImpl();
        } else if (version >= 19) {
            IMPL = new ViewParentCompatKitKatImpl();
        } else if (version >= 14) {
            IMPL = new ViewParentCompatICSImpl();
        } else {
            IMPL = new ViewParentCompatStubImpl();
        }
    }

这里来看其中一个就好:

static class ViewParentCompatStubImpl implements ViewParentCompatImpl {
        @Override
        public boolean requestSendAccessibilityEvent(
                ViewParent parent, View child, AccessibilityEvent event) {
            // Emulate what ViewRootImpl does in ICS and above.
            if (child == null) {
                return false;
            }
            final AccessibilityManager manager = (AccessibilityManager) child.getContext()
                    .getSystemService(Context.ACCESSIBILITY_SERVICE);
            manager.sendAccessibilityEvent(event);
            return true;
        }

        @Override
        public boolean onStartNestedScroll(ViewParent parent, View child, View target,
                int nestedScrollAxes) {
            if (parent instanceof NestedScrollingParent) {
                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                        nestedScrollAxes);
            }
            return false;
        }
        ...... 

先看onStartNestedScroll()这个方法。这里面判断parent是不是NestedScrollingParent的一个实例。因为CoordinatorLayout继承了NestedScrollingParent, 所以最终调用了CoordinatorLayout的onStartNestedScroll()方法。

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent{
    ......
}

CoordinatorLayout的onStartNestedScroll()方法:

  public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

                lp.acceptNestedScroll(accepted);
            } else {
                lp.acceptNestedScroll(false);
            }
        }
        return handled;
    } 

在onStartNestedScroll()方法中会判断子View的LayoutParams是否设置了Behavior属性,如果都没有,则直接返回false。这里我们的子View没有设置Behavior,所以返回了false。

之后动作传到onTouchEvent()。

   @Override
    public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();

        MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = MotionEventCompat.getActionMasked(ev);

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
                if (getChildCount() == 0) {
                    return false;
                }
                if ((mIsBeingDragged = !mScroller.isFinished())) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }

                /*
                 * If being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                // Remember where the motion event started
                mLastMotionY = (int) ev.getY();
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            }
            ...... 

同样也会调用startNestedScroll(), 之后的流程和上面的一样了,但是这里onTouchEvent()会返回true。到最后是ACTION_UP。

          ......
            case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
                            mActivePointerId);

                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        flingWithNestedDispatch(-initialVelocity);
                    } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                            getScrollRange())) {
                        ViewCompat.postInvalidateOnAnimation(this);
                    }
                }
                mActivePointerId = INVALID_POINTER;
                endDrag();
                break;
                ...... 

这里mIsBeingDragged一直为false,先不管。看endDrag()。

  private void endDrag() {
        mIsBeingDragged = false;

        recycleVelocityTracker();
        stopNestedScroll();

        if (mEdgeGlowTop != null) {
            mEdgeGlowTop.onRelease();
            mEdgeGlowBottom.onRelease();
        }
    }

调用了stopNestedScroll()。

   @Override
    public void stopNestedScroll() {
        mChildHelper.stopNestedScroll();
    } 

NestedScrollingChildHelper​。

  public void stopNestedScroll() {
        if (mNestedScrollingParent != null) {
            ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
            mNestedScrollingParent = null;
        }
    }

这里mNestedScrollingParent为空,所以不会调用到CoordinatorLayout的onNestedStopScroll()。

NestedScrollView在onInterceptTouchEvent()和onTouchEvent()中都会调用startNestedScroll(),最后会调用到CoordinatorLayout的onStartNestedScroll()。但是如果CoordinatorLayout的子View都没有设置Behavior,则什么都不会发生。

上一篇下一篇

猜你喜欢

热点阅读