記錄NestedScrollView的一個小坑

記錄一個NestedScrollView的小坑


  需求是這樣的要實現上面一個banner,下面是一個TabLayout+Fragment+RecyclerView的佈局,然後要求頁面上滑時Tab要吸頂,然後列表滾動。這應該是很常見的一個佈局了。看到這個需求自然第一時間就想到了NestedScrollView,只需要重寫onNestedPreScroll,然後在裏面做一下判斷,當向下滑動距離小於頂部Banner高度時,父佈局消費掉滑動;當向下滑動距離大於Banner高度時由RecyclerView來消費滑動事件。

@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        boolean headerScrollUp = dy > 0 && getScrollY() < mNestedScrollHeight;
        boolean headerScrollDown = dy < 0 && getScrollY() > 0 && !target.canScrollVertically(-1);
        if (headerScrollUp || headerScrollDown) {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
        super.onNestedPreScroll(target, dx, dy, consumed, type);
    }

  代碼類似上面這樣,mNestedScrollHeight是Banner的高度。然後寫好代碼Run一下,嗯?沒反應?不生效?what fuck?是我的邏輯不對嗎?不可能啊?不禁陷入了程序猿的沉思。
DEBUG一下吧。在onNestedPreScroll裏面打個斷點,發現根本沒執行進來!這就很奇怪了,又查一查網上的大家的解決方案,大家不都是這麼做的嗎?於是又陷入了自我懷疑。
  折騰了一下午,還是沒折騰出來,期間還用其他方案來做嵌套滑動,效果都實現差不多了,就是實現方式非常的ugly。秉承着一個稍微有點代碼潔癖的程序猿的自覺,我還是決定搞清楚爲啥onNestedPreScroll沒有被調用。所以終極解決方案就是,read the fucking source code!
  由於這裏嵌套滑動的子佈局是RecyclerView,所以消費滑動的事件是由RecyclerView發出的,然後在RecyclerView的onTouchEvent,在ACTION_DOWN中發出的startNestedScroll,具體實現是在NestedScrollingChildHelper。通過再次DEBUG,我確定了RecyclerView的ViewParent確實是我自定義的StickyNestedScrollView(繼承自NestedScrollView),吃了個定心丸。然後回到RecyclerView的onTouchEvent,在ACTION_MOVE中調用了dispatchNestedPreScroll,向嵌套父佈局發起嵌套滑動的事件,交給父佈局先處理。dispatchNestedPreScroll的具體實現也是在NestedScrollingChildHelper,然後在裏面調用了ViewParentCompat.onNestedPreScroll,看一下ViewParentCompat.onNestedPreScroll的源碼。

    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy, int[] consumed, int type) {
        if (parent instanceof NestedScrollingParent2) {
            ((NestedScrollingParent2)parent).onNestedPreScroll(target, dx, dy, consumed, type);
        } else if (type == 0) {
            if (VERSION.SDK_INT >= 21) {
                try {
                    parent.onNestedPreScroll(target, dx, dy, consumed);
                } catch (AbstractMethodError var7) {
                    Log.e("ViewParentCompat", "ViewParent " + parent + " does not implement interface " + "method onNestedPreScroll", var7);
                }
            } else if (parent instanceof NestedScrollingParent) {
                ((NestedScrollingParent)parent).onNestedPreScroll(target, dx, dy, consumed);
            }
        }

    }

NestedScrollView實現的是NestedScrollingParent2,所以這裏調用了onNestedPreScroll(target, dx, dy, consumed, type),看到這裏,就發現,這個方法怎麼和我重寫的方法不太一樣啊?
  然後繼續看了一下繼承關係,NestedScrollingParent2接口繼承自NestedScrollingParent接口,然後增加了一同名的函數onNestedPreScroll,但是增加了一個參數;NestedScrollView實現了NestedScrollingParent2接口。而最終調用的是NestedScrollingParent2的onNestedPreScroll(target, dx, dy, consumed, type)方法。所以,我們需要繼承NestedScrollView,重寫NestedScrollingParent2的onNestedPreScroll(target, dx, dy, consumed, type)方法,在裏面做處理纔可以。就因爲這個同名方法,可坑死我了,以後遇到類似的事情可得長點心了。

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