美文网首页
CoordinatorLayout、Behavior和嵌套滑动(

CoordinatorLayout、Behavior和嵌套滑动(

作者: HoooChan | 来源:发表于2017-09-18 20:03 被阅读171次

    嵌套滑动

    一个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,则什么都不会发生。

    相关文章

      网友评论

          本文标题:CoordinatorLayout、Behavior和嵌套滑动(

          本文链接:https://www.haomeiwen.com/subject/cgnbsxtx.html