目录

图解系列之SwipeRefreshLayout原理(三)


  • 我们在上一篇分析了SwipeRefreshlayout中传统事件拦截实现下拉刷新的代码。
  • 本篇将分析其通过嵌套滑动实现下拉刷新的代码。
  • 在前面的嵌套滑动相关文章中,我们也分析了NestedScrollingParent 和NestedScrollingChild的使用流程,这里就不再细说,有需要的小伙伴可以前往搜索NestedScrollingParent 和NestedScrollingChild。
解码开始
  • ok,看到这里就默认你已经了解了NestedScrollingChild和NestedScrollingParent的使用
  • 为了便于描述,假设RV为实现了NestedScrollingChild接口的View,如RecyclerView,SRL为SwipeRefreshLayout的简称

图形:

  • 交互图如下:
sequenceDiagram participant RV participant SRL RV -->> SRL:1、进入滑动状态,调用startNestedScroll SRL -->> SRL:2、onStartNestedScroll被回调,执行代码块1 SRL -->> SRL:3、当onStartNestedScroll返回true时,NestedScrollAccepted将被调用,看代码块2 RV -->> SRL:4、RV处理滚动操作前调用dispatchNestedPreScroll通知SRL SRL -->> SRL:5、onNestedPreScroll被回调,看代码块3 RV -->> SRL:6、RV处理滚动操作后调用dispatchNestedScroll通知SRL SRL -->> SRL:7、onNestedScroll被回调,看代码块4 RV -->> SRL:8、RV处理滚动操作结束后调用stopNestedScroll()通知SRL SRL -->> SRL:9、stopNestedScroll()被回调,主要重置一些状态变量,看代码块5

代码块(对图形描述的补充):

  • 代码块1:
1
2
3
4
5
        @Override
          public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
              //reture true时SRL将接受嵌套滑动,主要是判断是否在非刷新状态中和滚动轴为垂直的
              return isEnabled() && !mReturningToStart && !mRefreshing
                      && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
  • 代码块2: public void onNestedScrollAccepted(View child, View target, int axes) { // Reset the counter of how much leftover scroll needs to be consumed. mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); // Dispatch up to the nested parent

            //通知SRL的父类,如CoordinatorLayout
            startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
    
           //做一些字段的初始化
            mTotalUnconsumed = 0; //记录SRL一共使用了多少y值得量
            mNestedScrollInProgress = true;  //标记嵌套滑动开始此时传统触摸事件模式失效
        }
    
  • 代码块3:

     @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    
            //执行该代码的情况是你往下拖拽然后网上拖拽,此时需要优先于RV的滚动以便恢复刷新View的状态
            //dy大于0为向上滑动产生,而mTotalUnconsumed大于0说明SRL已经有消耗过y值了
            if (dy > 0 && mTotalUnconsumed > 0) {
                if (dy > mTotalUnconsumed) {
                    consumed[1] = dy - (int) mTotalUnconsumed;
                    mTotalUnconsumed = 0;
                } else {
                    mTotalUnconsumed -= dy;
                    consumed[1] = dy;
                }
                //将刷新view移动回mTotalUnconsumed只对应的位置,正值view向下移动,负值向上移动
                moveSpinner(mTotalUnconsumed);
            }
    
            //自定义View的情况,可不理会
            if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
                    && Math.abs(dy - consumed[1]) > 0) {
                mCircleView.setVisibility(View.GONE);
            }
    
            // Now let our nested parent consume the leftovers
            //同样,将嵌套滑动向上传递
            final int[] parentConsumed = mParentScrollConsumed;
            if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
                consumed[0] += parentConsumed[0];
                consumed[1] += parentConsumed[1];
            }
        }
    
  • 代码块4:

      @Override
        public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
                final int dxUnconsumed, final int dyUnconsumed) {
            // Dispatch up to the nested parent first
            //同样,消费嵌套滑动数据前先向上传递,让parent先消费
            dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                    mParentOffsetInWindow);
            //当parent有消费时,SRL相对于Parent的位置可能会改变,所以要结合mParentOffsetInWindow[1]的值来做处理
            final int dy = dyUnconsumed + mParentOffsetInWindow[1];
           //dy<0为从上向下滑动
           if (dy < 0 && !canChildScrollUp()) {
               //如果canChildScrollUp()为false,及RV已经滑到顶部了,不能再下拉了,此时自然是SRL的刷新View要出现了
                mTotalUnconsumed += Math.abs(dy);
                //调用moveSpinner来控制SRL刷新View的移动
                moveSpinner(mTotalUnconsumed);
            }
        }
    
  • 代码块5:

     @Override
        public void onStopNestedScroll(View target) {
            mNestedScrollingParentHelper.onStopNestedScroll(target);
            //重置变量为false
            mNestedScrollInProgress = false;
            // Finish the spinner for nested scrolling if we ever consumed any
            // unconsumed nested scroll
            if (mTotalUnconsumed > 0) {
            //类似于手势操作up的处理,结束时判断刷新View此时的状态是应该进入刷新状态还是回复到默认位置,代码看下面
    
                finishSpinner(mTotalUnconsumed);
                mTotalUnconsumed = 0;
            }
            // Dispatch up our nested parent
            stopNestedScroll();
            }        
    
  • finishSpinner 方法源码

     private void finishSpinner(float overscrollTop) {
            if (overscrollTop > mTotalDragDistance) {
                setRefreshing(true, true /* notify */);
            } else {
                // cancel refresh
                mRefreshing = false;
                mProgress.setStartEndTrim(0f, 0f);
                Animation.AnimationListener listener = null;
                if (!mScale) {
                    listener = new Animation.AnimationListener() {
    
                        @Override
                        public void onAnimationStart(Animation animation) {
                        }
    
                        @Override
                        public void onAnimationEnd(Animation animation) {
                            if (!mScale) {
                                startScaleDownAnimation(null);
                            }
                        }
    
                        @Override
                        public void onAnimationRepeat(Animation animation) {
                        }
    
                    };
                }
                animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
                mProgress.setArrowEnabled(false);
            }
        }
    
总结
  • 本次我们就完成了SRL嵌套滑动方面的分析,相信此时的你对嵌套滑动已经并不陌生了
  • 后面将会自己从0到1来实现一个刷新控件