美文网首页
CoordinatorLayout,嵌套滑动,自定义Behavi

CoordinatorLayout,嵌套滑动,自定义Behavi

作者: 暴走的小青春 | 来源:发表于2018-12-13 20:20 被阅读76次

上文说过了当触摸appbarLayout后appbarLayout滑动了,nestScrollview也跟着滑动的原理后,那姐下来分析下,为何nesrScrollview滑动了,appbarLayout也会跟着滑动,这也是嵌套滑动的核心所在:
首先我们知道当触摸了nestScrollview后,实际上事件先到CoordinatorLayout然后分发给appbarLayout的onInterceptTouchEvent方法

@Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
        if (mTouchSlop < 0) {
            mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
        }

        final int action = ev.getAction();

        // Shortcut since we're being dragged
        if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
            return true;
        }

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                mIsBeingDragged = false;
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) {
                    mLastMotionY = y;
                    mActivePointerId = ev.getPointerId(0);
                    ensureVelocityTracker();
                }
                break;
            }
       

当然我们触摸的地方不是在appbarLayout的区域内,所以不会拦截,这导致了CoordinatorLayout也不会拦截,所以交给了nestScrollview的onTouchevent处理,我们来先看下down这个方法:

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

       MotionEvent vtev = MotionEvent.obtain(ev);

       final int actionMasked = ev.getActionMasked();

       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 = ev.getPointerId(0);
               //重点这个方法
               startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
               break;
           }

重点是startNestScroll方法,此方法最终会调用coordinatorLayout的
onStartNestedScroll方法,很显然返回值会给每个子view的layoutParams设置acceptNestedScroll来标示.

 @Override
  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);
          if (view.getVisibility() == View.GONE) {
              // If it's GONE, don't dispatch
              continue;
          }
          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;
  }

再来看下nestScrollview的move方法

case MotionEvent.ACTION_MOVE:
               final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
               if (activePointerIndex == -1) {
                   Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
                   break;
               }

               final int y = (int) ev.getY(activePointerIndex);
               int deltaY = mLastMotionY - y;
               if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
                       ViewCompat.TYPE_TOUCH)) {
                   deltaY -= mScrollConsumed[1];
                   vtev.offsetLocation(0, mScrollOffset[1]);
                   mNestedYOffset += mScrollOffset[1];
               }
               if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                   final ViewParent parent = getParent();
                   if (parent != null) {
                       parent.requestDisallowInterceptTouchEvent(true);
                   }
                   mIsBeingDragged = true;
                   if (deltaY > 0) {
                       deltaY -= mTouchSlop;
                   } else {
                       deltaY += mTouchSlop;
                   }
               }
               if (mIsBeingDragged) {
                   // Scroll to follow the motion event
                   mLastMotionY = y - mScrollOffset[1];

                   final int oldY = getScrollY();
                   final int range = getScrollRange();
                   final int overscrollMode = getOverScrollMode();
                   boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS
                           || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);

                   // Calling overScrollByCompat will call onOverScrolled, which
                   // calls onScrollChanged if applicable.
                   if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0,
                           0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
                       // Break our velocity if we hit a scroll barrier.
                       mVelocityTracker.clear();
                   }

                   final int scrolledDeltaY = getScrollY() - oldY;
                   final int unconsumedY = deltaY - scrolledDeltaY;
                   if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
                           ViewCompat.TYPE_TOUCH)) {
                       mLastMotionY -= mScrollOffset[1];
                       vtev.offsetLocation(0, mScrollOffset[1]);
                       mNestedYOffset += mScrollOffset[1];
                   } else if (canOverscroll) {
                       ensureGlows();
                       final int pulledToY = oldY + deltaY;
                       if (pulledToY < 0) {
                           EdgeEffectCompat.onPull(mEdgeGlowTop, (float) deltaY / getHeight(),
                                   ev.getX(activePointerIndex) / getWidth());
                           if (!mEdgeGlowBottom.isFinished()) {
                               mEdgeGlowBottom.onRelease();
                           }
                       } else if (pulledToY > range) {
                           EdgeEffectCompat.onPull(mEdgeGlowBottom, (float) deltaY / getHeight(),
                                   1.f - ev.getX(activePointerIndex)
                                           / getWidth());
                           if (!mEdgeGlowTop.isFinished()) {
                               mEdgeGlowTop.onRelease();
                           }
                       }
                       if (mEdgeGlowTop != null
                               && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
                           ViewCompat.postInvalidateOnAnimation(this);
                       }
                   }
               }
               break;

重点分为两个部分,可以看到nestScrollview首先要调用dispatchNestedPreScroll这个方法,也就是滑动前回调用次方法,最终调用的是CoordinatorLayout的onNestedPreScroll方法

@Override
   public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
       int xConsumed = 0;
       int yConsumed = 0;
       boolean accepted = false;

       final int childCount = getChildCount();
       for (int i = 0; i < childCount; i++) {
           final View view = getChildAt(i);
           if (view.getVisibility() == GONE) {
               // If the child is GONE, skip...
               continue;
           }

           final LayoutParams lp = (LayoutParams) view.getLayoutParams();
           if (!lp.isNestedScrollAccepted(type)) {
               continue;
           }

           final Behavior viewBehavior = lp.getBehavior();
           if (viewBehavior != null) {
               mTempIntPair[0] = mTempIntPair[1] = 0;
               viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);

               xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                       : Math.min(xConsumed, mTempIntPair[0]);
               yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                       : Math.min(yConsumed, mTempIntPair[1]);

               accepted = true;
           }
       }

       consumed[0] = xConsumed;
       consumed[1] = yConsumed;

       if (accepted) {
           onChildViewsChanged(EVENT_NESTED_SCROLL);
       }
   }

这里我们知道了会根据标示来调用onNestedPreScroll方法来看下这个方法通常是通过我们来重写的,来决定nestScrollview是否消耗了滑动的距离从而来判断起滑动不滑动:

void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed,
         @NestedScrollType int type);

if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
                     ViewCompat.TYPE_TOUCH)) {
                 deltaY -= mScrollConsumed[1];
                 vtev.offsetLocation(0, mScrollOffset[1]);
                 mNestedYOffset += mScrollOffset[1];
             }
             if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                 final ViewParent parent = getParent();
                 if (parent != null) {
                     parent.requestDisallowInterceptTouchEvent(true);
                 }
                 mIsBeingDragged = true;
                 if (deltaY > 0) {
                     deltaY -= mTouchSlop;
                 } else {
                     deltaY += mTouchSlop;
                 }
             }

这里我们看到一旦behavior的onNestedPreScroll里面的int[] consumed
返回的数值是滑动的是将要滑动的距离的话,那很明显就不会滑动了
我们再来看下appbarlayout里调用的onNestedScroll方法

@Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                View target, int dx, int dy, int[] consumed, int type) {
            if (dy != 0) {
                int min, max;
                if (dy < 0) {
                    // We're scrolling down
                    min = -child.getTotalScrollRange();
                    max = min + child.getDownNestedPreScrollRange();
                } else {
                    // We're scrolling up
                    min = -child.getUpNestedPreScrollRange();
                    max = 0;
                }
                if (min != max) {
                    consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
                }
            }
        }

终于真相大白了,原来当触摸在nestscrollview并且appBarlayout可滑的情况下先滑动的还是appbarLayout,根据上文可知,appbarlayout滑动了,
nestScrollview也会滑动,滑到了消失的时候就会调用onNestedScroll

void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
           int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type);

来看下这个里面的参数,很明显又是消耗和未消耗,但这里是滑动的情况,举个例子,一般情况下你的nestScrollview滑动多少,这个view会向下滑多少,dyUnconsumed一直为0,但是当你往下滑滑倒最底端了,这个滑动你却消耗不了,所以这个dyUnconsumed会不为0:
好了,关于nestScrollview滑动appbarlayout跟着滑动的秘密也就明白了。

相关文章

网友评论

      本文标题:CoordinatorLayout,嵌套滑动,自定义Behavi

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