嵌套滑動通用解決方案--NestedScrollingParent2

之前寫過一篇嵌套滑動–NestedScroll-項目實例(淘寶首頁缺陷),及CoordinatorLayout 和 AppbarLayout 聯動原理,比較了淘寶和京東首頁的滑動效果,分析了效果呈現差別的原因,給出了大致的解決方案。
當時沒有給出demo,只有代碼片段,可能導致閱讀起來不很清晰,所以這篇就專門再來詳細分析相關知識,給出通用的嵌套滑動的解決方案,且附上GitHub的Demo。

本文相關代碼Demo Github地址,有幫助的話Star一波吧。

一、問題及解決方案

先來看一張圖:
京東首頁
這是京東的首頁,忽略頂部和頂部,大致理解視圖結構就是:最外層爲多佈局的RecyclerView,最後一個item是tabLayout+ViewPager,ViewPager的每個fragment內也是RecyclerView。這是電商App首頁常用的佈局方式。

再來看下滑動起來的效果圖:
在這裏插入圖片描述
可見,在向上滑動頁面時,當tabLayout滑動到頂部時,外層RecyclerView停止滑動,此時tabLayout即爲吸頂狀態,接着會 滑動ViewPager中的內層RecyclerView。向下滑動時,如果tabLayout是吸頂狀態,那麼會先滑動內層RecyclerView,然後再滑外層RecyclerView。

那麼,如果我們 直接 按上述佈局結構來實現,會是京東這種效果嗎?答案是否定的,效果如下?

按分析的view結構直接實現
可見,在tabLayout是吸頂狀態,無法繼續滑動內層RecyclerView(擡起手指繼續滑也不行)。 (點擊查看相關代碼

那麼該咋辦呢?根據滑動衝突的相關知識,我們知道一定是外層RecyclerView攔截了觸摸事件,內層RecyclerView無法獲取事件,就無法滑動了。那麼是否可以在tabLayout吸頂時,外層不要攔截事件,從而內層RecyclerView獲取事件進而滑動呢?

這是可行的,但是在tabLayout滑動到頂部後,必須擡起手指,重新滑動,內層RecyclerView才能繼續滑動。 這是爲啥呢?開頭提到的博客中有說明:

從view事件分發機制 我們知道,當parent View攔截事件後,那同一事件序列的事件會直接都給parent處理,子view不會接受事件了。所以按照正常處理滑動衝突的思路處理–當tab沒到頂部時,parent攔截事件,tab到頂部時 parent就不攔截事件,但是由於手指沒擡起來,所以這一事件序列還是繼續給parent,不會到內部RecyclerView,所以商品流就不會滑動了。

解決方案只能是嵌套滑動佈局了。代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<com.hfy.demo01.module.home.touchevent.view.NestedScrollingParent2LayoutImpl3 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nested_scrolling_parent2_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.hfy.demo01.module.home.toucheve

看到我們把外層RecyclerView的根佈局換成了NestedScrollingParent2LayoutImpl3,運行後發現確實解決了上述問題,滑動效果同京東一致。
那NestedScrollingParent2LayoutImpl3這是啥呢?NestedScrollingParent2LayoutImpl3是繼承NestedScrollingParent2的LinearLayout,用於處理上述嵌套滑動帶來的問題。(點擊查看NestedScrollingParent2LayoutImpl3的實現

效果如下:
在這裏插入圖片描述

如果不關心原理及實現,到這了就結束了,因爲NestedScrollingParent2LayoutImpl3就可以解決以上問題。

二、NestedScrollingParent2LayoutImpl3的實現原理

2.1 先來回顧下嵌套滑動機制。

如果還不瞭解嵌套滑動以及NestedScrollingParent2,建議先閱讀此篇博客自定義View事件之進階篇(一)-NestedScrolling(嵌套滑動)機制,再接着往下閱讀。

NestedScrolling(嵌套滑動)機制,簡單說來就是:產生嵌套滑動的子view,在滑動前,先詢問 嵌套滑動對應的父view 是否優先處理 事件、以及消費多少事件,然後把消費後剩餘的部分 繼續給到 子view。 可以理解爲一個事件序列分發兩次。產生嵌套滑動的子view要實現接口NestedScrollingChild2、父view要實現接口NestedScrollingParent2。

常用的RecyclerView就是實現了NestedScrollingChild2,而NestedScrollView則是既實現了NestedScrollingChild2又實現了NestedScrollingParent2。

通常我們要自行手動處理的就是RecyclerView作爲嵌套滑動子view的情況。NestedScrollView一般直接作爲根佈局用來解決嵌套滑動。

2.2 再來看看NestedScrollView嵌套RecyclerView

關於NestedScrollView嵌套RecyclerView的情況,即頭部和列表可以一起滑動。如下圖:
NestedScrollView嵌套RecyclerView
參考這篇實名反對《阿里巴巴Android開發手冊》中NestedScrollView嵌套RecyclerView的用法。從此篇文章分析結論得知,NestedScrollView嵌套RecyclerView雖然可以實現效果,但是RecyclerView會瞬間加載所有item,RecyclerView失去的view回收的特性。 作者最後建議使用RecyclerView多佈局。
但其實在真實應用中,可能 頭部 和 列表 的數據來自不同的接口,當列表的數據請求失敗時要展示缺省圖,但頭部還是會展示。這時頭部和列表 分開實現 是比較好的選擇。

這裏給出解決方案:

<?xml version="1.0" encoding="utf-8"?>
<com.hfy.demo01.module.home.touchevent.view.NestedScrollingParent2LayoutImpl2 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_head"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:padding="15dp"
        android:text="我是頭部。 最外層是NestedScrollingParent2LayoutImpl2"
        android:textColor="#fff"
        android:textSize="20dp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/design_default_color_primary" />

</com.hfy.demo01.module.home.touchevent.view.NestedScrollingParent2LayoutImpl2>

NestedScrollingParent2LayoutImpl2同樣是實現了NestedScrollingParent2。(點擊查看NestedScrollingParent2LayoutImpl2的實現

效果如下,可見滑動流暢,臨界處不用擡起手指重新滑,且查看日誌不是一次加載完item。
在這裏插入圖片描述

先看下NestedScrollingParent2LayoutImpl2的實現,要簡單一些,接着再看NestedScrollingParent2LayoutImpl3實現原理,整體思路是一致的。

/**
 * 處理 header + recyclerView
 * Description:NestedScrolling2機制下的嵌套滑動,實現NestedScrollingParent2接口下,處理fling效果的區別
 *
 */
public class NestedScrollingParent2LayoutImpl2 extends NestedScrollingParent2Layout implements NestedScrollingParent2 {


    private View mTopView;
    private View mRecylerVIew;

    private int mTopViewHeight;


    public NestedScrollingParent2LayoutImpl2(Context context) {
        this(context, null);
    }

    public NestedScrollingParent2LayoutImpl2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NestedScrollingParent2LayoutImpl2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
    }


    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }


    /**
     * 在嵌套滑動的子View未滑動之前,判斷父view是否優先與子view處理(也就是父view可以先消耗,然後給子view消耗)
     *
     * @param target   具體嵌套滑動的那個子類
     * @param dx       水平方向嵌套滑動的子View想要變化的距離
     * @param dy       垂直方向嵌套滑動的子View想要變化的距離 dy<0向下滑動 dy>0 向上滑動
     * @param consumed 這個參數要我們在實現這個函數的時候指定,回頭告訴子View當前父View消耗的距離
     *                 consumed[0] 水平消耗的距離,consumed[1] 垂直消耗的距離 好讓子view做出相應的調整
     * @param type     滑動類型,ViewCompat.TYPE_NON_TOUCH fling效果,ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //這裏不管手勢滾動還是fling都處理
        boolean hideTop = dy > 0 && getScrollY() < mTopViewHeight;
        boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1);
        if (hideTop || showTop) {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
    }


    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        //當子控件處理完後,交給父控件進行處理。
        if (dyUnconsumed < 0) {
            //表示已經向下滑動到頭
            scrollBy(0, dyUnconsumed);
        }
    }

    @Override
    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
        return false;
    }


    @Override
    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //這裏修改mRecylerVIew的高度爲屏幕高度,否則底部會出現空白。(因爲scrollTo方法是滑動子view,就把mRecylerVIew滑上去了)
        ViewGroup.LayoutParams layoutParams = mRecylerVIew.getLayoutParams();
        layoutParams.height = getMeasuredHeight();
        mRecylerVIew.setLayoutParams(layoutParams);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mTopView = findViewById(R.id.tv_head);
        mRecylerVIew = findViewById(R.id.recyclerView);
        if (!(mRecylerVIew instanceof RecyclerView)) {
            throw new RuntimeException("id RecyclerView should be RecyclerView!");
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTopViewHeight = mTopView.getMeasuredHeight();
    }

    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > mTopViewHeight) {
            y = mTopViewHeight;
        }
        super.scrollTo(x, y);
    }

}

主要就是再onNestedPreScroll中對臨界處做了處理:滑動RecyclerView時先滑動根佈局,使得頭部隱藏或顯示,然後再交給RecyclerView滑動。

2.3 NestedScrollingParent2LayoutImpl3的實現原理

代碼如下

/**
 * 處理RecyclerView 套viewPager, viewPager內的fragment中 也有RecyclerView,處理外層、內層 RecyclerView的嵌套滑動問題
 * 類似淘寶、京東首頁
 *
 */
public class NestedScrollingParent2LayoutImpl3 extends NestedScrollingParent2Layout {

    private final String TAG = this.getClass().getSimpleName();

    private RecyclerView mParentRecyclerView;


    private RecyclerView mChildRecyclerView;

    private View mLastItemView;


    public NestedScrollingParent2LayoutImpl3(Context context) {
        super(context);
    }

    public NestedScrollingParent2LayoutImpl3(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NestedScrollingParent2LayoutImpl3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
    }


    /**
     * 有嵌套滑動到來了,判斷父view是否接受嵌套滑動
     *
     * @param child            嵌套滑動對應的父類的子類(因爲嵌套滑動對於的父View不一定是一級就能找到的,可能挑了兩級父View的父View,child的輩分>=target)
     * @param target           具體嵌套滑動的那個子類
     * @param nestedScrollAxes 支持嵌套滾動軸。水平方向,垂直方向,或者不指定
     * @param type             滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes, int type) {
        //自己處理邏輯
        //這裏處理是接受 豎向的 嵌套滑動
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    /**
     * 在嵌套滑動的子View未滑動之前,判斷父view是否優先與子view處理(也就是父view可以先消耗,然後給子view消耗)
     *
     * @param target   具體嵌套滑動的那個子類,就是手指滑的那個 產生嵌套滑動的view
     * @param dx       水平方向嵌套滑動的子View想要變化的距離
     * @param dy       垂直方向嵌套滑動的子View想要變化的距離 dy<0向下滑動 dy>0 向上滑動
     * @param consumed 這個參數要我們在實現這個函數的時候指定,回頭告訴子View當前父View消耗的距離
     *                 consumed[0] 水平消耗的距離,consumed[1] 垂直消耗的距離 好讓子view做出相應的調整
     * @param type     滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //自己處理邏輯

        if (mLastItemView == null) {
            return;
        }

        int lastItemTop = mLastItemView.getTop();

        if (target == mParentRecyclerView) {
            handleParentRecyclerViewScroll(lastItemTop, dy, consumed);
        } else if (target == mChildRecyclerView) {
            handleChildRecyclerViewScroll(lastItemTop, dy, consumed);
        }
    }

    /**
     * 滑動外層RecyclerView時,的處理
     *
     * @param lastItemTop tab到屏幕頂部的距離,是0就代表到頂了
     * @param dy          目標滑動距離, dy>0 代表向上滑
     * @param consumed
     */
    private void handleParentRecyclerViewScroll(int lastItemTop, int dy, int[] consumed) {
        //tab上邊沒到頂
        if (lastItemTop != 0) {
            if (dy > 0) {
                //向上滑
                if (lastItemTop > dy) {
                    //tab的top>想要滑動的dy,就讓外部RecyclerView自行處理
                } else {
                    //tab的top<=想要滑動的dy,先滑外部RecyclerView,滑距離爲lastItemTop,剛好到頂;剩下的就滑內層了。
                    consumed[1] = dy;
                    mParentRecyclerView.scrollBy(0, lastItemTop);
                    mChildRecyclerView.scrollBy(0, dy - lastItemTop);
                }
            } else {
                //向下滑,就讓外部RecyclerView自行處理
            }
        } else {
            //tab上邊到頂了
            if (dy > 0){
                //向上,內層直接消費掉
                mChildRecyclerView.scrollBy(0, dy);
                consumed[1] = dy;
            }else {
                int childScrolledY = mChildRecyclerView.computeVerticalScrollOffset();
                if (childScrolledY > Math.abs(dy)) {
                    //內層已滾動的距離,大於想要滾動的距離,內層直接消費掉
                    mChildRecyclerView.scrollBy(0, dy);
                    consumed[1] = dy;
                }else {
                    //內層已滾動的距離,小於想要滾動的距離,那麼內層消費一部分,到頂後,剩的還給外層自行滑動
                    mChildRecyclerView.scrollBy(0, -(Math.abs(dy)-childScrolledY));
                    consumed[1] = -(Math.abs(dy)-childScrolledY);
                }
            }
        }

    }

    /**
     * 滑動內層RecyclerView時,的處理
     *
     * @param lastItemTop tab到屏幕頂部的距離,是0就代表到頂了
     * @param dy
     * @param consumed
     */
    private void handleChildRecyclerViewScroll(int lastItemTop, int dy, int[] consumed) {
        //tab上邊沒到頂
        if (lastItemTop != 0) {
            if (dy > 0) {
                //向上滑
                if (lastItemTop > dy) {
                    //tab的top>想要滑動的dy,外層直接消耗掉
                    mParentRecyclerView.scrollBy(0, dy);
                    consumed[1] = dy;
                } else {
                    //tab的top<=想要滑動的dy,先滑外層,消耗距離爲lastItemTop,剛好到頂;剩下的就滑內層了。
                    mParentRecyclerView.scrollBy(0, lastItemTop);
                    consumed[1] = dy - lastItemTop;
                }
            } else {
                //向下滑,外層直接消耗
                mParentRecyclerView.scrollBy(0, dy);
                consumed[1] = dy;
            }
        }else {
            //tab上邊到頂了
            if (dy > 0){
                //向上,內層自行處理
            }else {
                int childScrolledY = mChildRecyclerView.computeVerticalScrollOffset();
                if (childScrolledY > Math.abs(dy)) {
                    //內層已滾動的距離,大於想要滾動的距離,內層自行處理
                }else {
                    //內層已滾動的距離,小於想要滾動的距離,那麼內層消費一部分,到頂後,剩的外層滑動
                    mChildRecyclerView.scrollBy(0, -childScrolledY);
                    mParentRecyclerView.scrollBy(0, -(Math.abs(dy)-childScrolledY));
                    consumed[1] = dy;
                }
            }
        }
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        //直接獲取外層RecyclerView
        mParentRecyclerView = getRecyclerView(this);
        Log.i(TAG, "onFinishInflate: mParentRecyclerView=" + mParentRecyclerView);

        //關於內層RecyclerView:此時還獲取不到ViewPager內fragment的RecyclerView,需要在加載ViewPager後 fragment可見時 傳入
    }

    private RecyclerView getRecyclerView(ViewGroup viewGroup) {
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            if (childAt instanceof RecyclerView) {
                if (mParentRecyclerView == null) {
                    return (RecyclerView) childAt;
                }
            }
        }
        return null;
    }

    /**
     * 傳入內部RecyclerView
     *
     * @param childRecyclerView
     */
    public void setChildRecyclerView(RecyclerView childRecyclerView) {
        mChildRecyclerView = childRecyclerView;
    }


    /**
     * 外層RecyclerView的最後一個item,即:tab + viewPager
     * 用於判斷 滑動 臨界位置
     *
     * @param lastItemView
     */
    public void setLastItem(View lastItemView) {
        mLastItemView = lastItemView;
    }
}

NestedScrollingParent2LayoutImpl3 繼承自 NestedScrollingParent2Layout。NestedScrollingParent2Layout是繼承自 LinearLayout implements 並實現了NestedScrollingParent2,主要處理了通用的方法實現。

/**
 * Description:  通用 滑動嵌套處理佈局,用於處理含有{@link androidx.recyclerview.widget.RecyclerView}的嵌套套滑動
 */
public class NestedScrollingParent2Layout extends LinearLayout implements NestedScrollingParent2 {


    private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

    public NestedScrollingParent2Layout(Context context) {
        super(context);
    }

    public NestedScrollingParent2Layout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NestedScrollingParent2Layout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 有嵌套滑動到來了,判斷父view是否接受嵌套滑動
     *
     * @param child            嵌套滑動對應的父類的子類(因爲嵌套滑動對於的父View不一定是一級就能找到的,可能挑了兩級父View的父View,child的輩分>=target)
     * @param target           具體嵌套滑動的那個子類
     * @param nestedScrollAxes 支持嵌套滾動軸。水平方向,垂直方向,或者不指定
     * @param type             滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes, int type) {
        //自己處理邏輯
        return true;
    }

    /**
     * 當父view接受嵌套滑動,當onStartNestedScroll方法返回true該方法會調用
     *
     * @param type 滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
    }

    /**
     * 在嵌套滑動的子View未滑動之前,判斷父view是否優先與子view處理(也就是父view可以先消耗,然後給子view消耗)
     *
     * @param target   具體嵌套滑動的那個子類
     * @param dx       水平方向嵌套滑動的子View想要變化的距離
     * @param dy       垂直方向嵌套滑動的子View想要變化的距離 dy<0向下滑動 dy>0 向上滑動
     * @param consumed 這個參數要我們在實現這個函數的時候指定,回頭告訴子View當前父View消耗的距離
     *                 consumed[0] 水平消耗的距離,consumed[1] 垂直消耗的距離 好讓子view做出相應的調整
     * @param type     滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //自己處理邏輯
    }

    /**
     * 嵌套滑動的子View在滑動之後,判斷父view是否繼續處理(也就是父消耗一定距離後,子再消耗,最後判斷父消耗不)
     *
     * @param target       具體嵌套滑動的那個子類
     * @param dxConsumed   水平方向嵌套滑動的子View滑動的距離(消耗的距離)
     * @param dyConsumed   垂直方向嵌套滑動的子View滑動的距離(消耗的距離)
     * @param dxUnconsumed 水平方向嵌套滑動的子View未滑動的距離(未消耗的距離)
     * @param dyUnconsumed 垂直方向嵌套滑動的子View未滑動的距離(未消耗的距離)
     * @param type         滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        //自己處理邏輯
    }

    /**
     * 嵌套滑動結束
     *
     * @param type 滑動類型,ViewCompat.TYPE_NON_TOUCH fling 效果ViewCompat.TYPE_TOUCH 手勢滑動
     */
    @Override
    public void onStopNestedScroll(@NonNull View child, int type) {
        mNestedScrollingParentHelper.onStopNestedScroll(child, type);
    }

    @Override
    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
        //自己判斷是否處理
        return false;
    }

    @Override
    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) {
        //自己處理邏輯
        return false;
    }


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

}

實現原理主要在onNestedPreScroll方法,即嵌套滑動的子view滑動前,詢問對應的父view是否優先處理,以及處理多少。

所以無論滑動外城RecyclerView還是內層RecyclerView,都會詢問NestedScrollingParent2LayoutImpl3,即都會走到onNestedPreScroll方法。然後根據tabLayout的位置以及滑動的方向,決定是滑動外層RecyclerView還是滑內層,以及滑動多少。相當於一個事假序列分發了兩次,避免了常規事件分發 父view攔截後子view無法處理的問題。

onNestedPreScroll中的具體處理,請看代碼,有詳細註釋。要結合滑動實際情況去理解,便於遇到其他情況也能同樣處理。

這裏列出已經實現的處理三種嵌套滑動的方案

  • NestedScrollingParent2LayoutImpl1:處理 header + tab + viewPager + recyclerView
  • NestedScrollingParent2LayoutImpl2: 處理 header + recyclerView
  • NestedScrollingParent2LayoutImpl3:處理RecyclerView 套viewPager, viewPager內的fragment中 也有RecyclerView,處理外層、內層 RecyclerView的嵌套滑動問題,類似淘寶、京東首頁。

在這裏插入圖片描述

Demo Github地址,有幫助的話Star一波吧。

歡迎關注
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章