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(); } }