美文网首页UI效果仿写Android 知识需要使用
NestedScrollView与RecyclerView的嵌套

NestedScrollView与RecyclerView的嵌套

作者: jeffrey要努力 | 来源:发表于2018-03-27 14:41 被阅读242次
    ScreenRecord.gif

    使用NestedScrollView优化嵌套RecyclerView

    在开发中经常会遇到ScrollView嵌套RecyclerView的情况。
    例如界面需要一个banner,一段介绍文字,还有个列表,banner要可以划出界面,介绍文字要滑动后固定在顶部

    开发中有两种解决办法 :
    1 整个页面使用RecyclerView,根据类型返回不同的ViewHolder,这也是我正常用的,这次学习下下面的方法
    2 使用NestedScrollView 包裹RecyclerView.(这个可以直接使用,但是需要点小优化)

    NestedScrollView

    NestedScrollView 和scrollView一样的使用,直接包裹一个子控件就可以了,它实现了 NestedScrollingParent, NestedScrollingChild2这两个方法

    实现NestedScrollingParent的意思就是 我是个嵌套滑动的父控件,我可以和子滑动控件一起处理滑动事件。NestedScrollView嵌套RecyclerView主要就是关注这个
    实现NestedScrollingChild2的意思是 我是个嵌套滑动的子控件,我滑动的时候要告诉父嵌套滑动控件,滑动之前要问问他是否消耗滑动事件。消耗掉的话 我就不滑动了,这个是NestedScrollView作为子控件的时候关注的

    而RecyclerView则是实现了NestedScrollingChild2 他只能作为滑动的嵌套子控件

    在滑动前通知父控件,如果父控件消耗了滑动距离 则返回的consumed里面的值就不为0
    abstract boolean    dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type)
    Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
    
    滑动的时候告诉父控件,因为NestedScrollView和RecylerView里面已经处理好了,我们这次没用到
    abstract boolean    dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type)
    Dispatch one step of a nested scroll in progress.
    
    是否有嵌套的滑动父控件
    abstract boolean    hasNestedScrollingParent(int type)
    Returns true if this view has a nested scrolling parent for the given input type.
    
    告诉父控件开始滑动了,如果有父滑动控件,并且父滑动控件想要和子控件一起处理滑动的话,就会返回True
    abstract boolean    startNestedScroll(int axes, int type)
    Begin a nestable scroll operation along the given axes, for the given input type.
    
    停止嵌套滑动了
    abstract void   stopNestedScroll(int type)
    Stop a nested scroll in progress for the given input type.
    

    RecyclerView中有个成员变量

    private NestedScrollingChildHelper mScrollingChildHelper;
    private NestedScrollingChildHelper getScrollingChildHelper() {
            if (mScrollingChildHelper == null) {
                mScrollingChildHelper = new NestedScrollingChildHelper(this);
            }
            return mScrollingChildHelper;
        }
    

    对应的NestedScrollView里面

    private final NestedScrollingParentHelper mParentHelper;
    mParentHelper = new NestedScrollingParentHelper(this);
    

    NestedScrollingChildHelper和NestedScrollingParentHelper都是系统提供的帮助类,已经封装好滑动调用逻辑,我们的关注点其实是在接口的回调上面。例如NestedScrollView的接口NestedScrollingParent

    abstract int    getNestedScrollAxes()
    Return the current axes of nested scrolling for this NestedScrollingParent.
    
    abstract boolean    onNestedFling(View target, float velocityX, float velocityY, boolean consumed)
    Request a fling from a nested scroll.
    
    abstract boolean    onNestedPreFling(View target, float velocityX, float velocityY)
    React to a nested fling before the target view consumes it.
    
    abstract void   onNestedPreScroll(View target, int dx, int dy, int[] consumed)
    React to a nested scroll in progress before the target view consumes a portion of the scroll.
    
    abstract void   onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
    React to a nested scroll in progress.
    
    abstract void   onNestedScrollAccepted(View child, View target, int axes)
    React to the successful claiming of a nested scroll operation.
    
    abstract boolean    onStartNestedScroll(View child, View target, int axes)
    React to a descendant view initiating a nestable scroll operation, claiming the nested scroll operation if appropriate.
    
    abstract void   onStopNestedScroll(View target)
    React to a nested scroll operation ending.
    

    要实现效果的话:
    1 当banner在顶部的时候 不管手指在哪滑动,都是NestedScrollView滑动
    2 当banner已经划过顶部的时候,手指在RecyclerView中滑动的时候,是RecyclerView滑动

    我们demo中这个阀值就是banner的高度,上面说的是相应切换,其实并没有,只是父控件有没有消耗掉滑动距离的问题。子控件滑动前都会告诉父控件,父控件消耗掉了话,子控件就不做响应
    在RecyclerView的OnTouchEvent中

     if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
        dx -= mScrollConsumed[0];
        dy -= mScrollConsumed[1];
        vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
        // Updated the nested offsets
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];
     }
    

    dx和dy都要减去父控件消耗的距离,如果父控件把滑动距离全消耗掉了的话,那么RecyclerView就不会滑动了
    我继承了NestedScrollView并重写了OnNestedPreScroll,逻辑是如果NestedScrollView的滑动距离没有超过阀值,NestedScrollView就消耗掉全部的距离,超过了就全交给子控件自己处理。
    只要做这一件事就可以了 就是这么简单
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    super.onNestedPreScroll(target, dx, dy, consumed);

            if (mScrollY < mParentScrollHeight) {
                consumed[0] = dx;
                consumed[1] = dy;
                scrollBy(0, dy);
            }
    
            Log.d(TAG,"dx " + dx + " dy "+ dy +  " " + consumed[0]  + " " + consumed[1] + " scrollY " + mScrollY);
        }
    

    还有个问题是NestedScrollView嵌套RecyclerView的话,滑动问题解决了,但是RecyclerView会绘制出所有的item,如果列表很大的话就完蛋了,所以我们需要固定RecyclerView的高度。
    高度就是rootView的高度-栏目类型view的高度

     rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                  rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                  int rvNewHeight = rootView.getHeight() - topView2.getHeight();
                  rv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,rvNewHeight));
     }
    

    另外还遇到个问题,NestedScrollView嵌套RecyclerView时,固定高度后打开界面时会自动滑到底部。只需要在NestedScrollView的子view中加入 android:descendantFocusability="blocksDescendants"

    最后 demo地址

    相关文章

      网友评论

        本文标题:NestedScrollView与RecyclerView的嵌套

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