Android 的嵌套滚动,实现比拟不便

  • 横着滚动,ViewPager2
  • 竖着滚动,NestedScrollingParent

顶上,有一个头部视图 header,

两头,有一个菜单视图 menu,

上面的是,内容视图, 一个 ViewPager2,蕴含几个 Tab,

Tab 外面是列表 RecyclerView

本文,次要参考 hongyangAndroid/Android-StickyNavLayout

Java 实现

基于 LinearLayout ,增加 NestedScrollingParent

子 View 开始滚动时,申请父 View 是否开始承受嵌套滚动,

SCROLL_AXIS_HORIZONTAL = 1

SCROLL_AXIS_VERTICAL = 2

程度方向,返回 false, 示意不承受;

( 不承受,则程度滚动,对竖直方向的滚动,没有干预 )

竖直方向,返回 true, 示意承受。

public class StickyNavLayout extends LinearLayout implements NestedScrollingParent{    @Override    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)    {        if (nestedScrollAxes == 1){            return false;        }        else{            return true;        }    }}

返回嵌套滚动的方向

    @Override    public int getNestedScrollAxes()    {        return ViewCompat.SCROLL_AXIS_VERTICAL;    }

子视图纵向滚动,带动父视图的纵向滚动

指标视图执行嵌套滚动前的回调,

dx,dy 为产生的滚动间隔,( 指标视图,就是拖动的子视图, RecyclerView )

( 纵向滚动, dx 为 0 )

consumed 为父 View 耗费的滚动间隔

@Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)    {       // 依据子视图的滚动偏移 dy       // 和父视图的滚动偏移 getScrollY()       // 确定子视图纵向滚动,带动父视图的纵向滚动        boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;        boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1);        if (hiddenTop || showTop)        {            scrollBy(0, dy);            consumed[1] = dy;        }    }

成果加强, 动画

往上轻滚,就把 header 遮蔽;

往下轻滚,就显示 header

    private int TOP_CHILD_FLING_THRESHOLD = 3;        @Override    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed)    {        //如果是 recyclerView 依据判断第一个元素是哪个地位,能够判断是否耗费                //这里判断,如果第一个元素的地位是大于 TOP_CHILD_FLING_THRESHOLD 的               //认为曾经被耗费,在 animateScroll 里不会对 velocityY<0 时做解决                if (target instanceof RecyclerView && velocityY < 0) {            // 对子视图为 RecyclerView, 专门解决                        final RecyclerView recyclerView = (RecyclerView) target;            final View firstChild = recyclerView.getChildAt(0);            final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);            consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;        }        // 动效        animateScroll(velocityY,  700, consumed);        return true;    }
动画滚动

应用 ValueAnimator ,做滚动动画

   private ValueAnimator mOffsetAnimator;   private void animateScroll(float velocityY, final int duration,boolean consumed) {        final int currentOffset = getScrollY();        final int topHeight = mTop.getHeight();        if (mOffsetAnimator == null) {            // 之前不存在动画,就新建            mOffsetAnimator = new ValueAnimator();            mOffsetAnimator.setInterpolator(mInterpolator);            mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                @Override                public void onAnimationUpdate(ValueAnimator animation) {                    if (animation.getAnimatedValue() instanceof Integer) {                        scrollTo(0, (Integer) animation.getAnimatedValue());                    }                }            });        } else {            // 之前存在动画,就勾销            mOffsetAnimator.cancel();        }        mOffsetAnimator.setDuration(Math.min(duration, 600));        if (velocityY >= 0) {            // 向上滚动            // 暗藏 header            mOffsetAnimator.setIntValues(currentOffset, topHeight);            mOffsetAnimator.start();        }else if( !consumed ){            // 向下滚动            // 显示 header                // 如果子 View 没有耗费 down 事件 那么就让本身滑到 0 地位                mOffsetAnimator.setIntValues(currentOffset, 0);                mOffsetAnimator.start();        }    }

github repo