SwipeRefreshLayout 在 RecyclerView 空白時下拉失效分析

爲一箇舊頁面添加空白頁後驗證時出現了意想不到的狀況,SwipeRefreshLayoutRecyclerView 空白時下拉失效。佈局是 SwipeRefreshLayout 裏面有一個 RecyclerView 還有一個 ViewStub 用來顯示空白頁面。出現狀況就是當 RecyclerView 沒有數據,展現出空白頁以後,SwipeRefreshLayout 就沒法下拉。函數

一開始覺得是空白頁擋住 SwipeRefreshLayout ,後面迅速又否認來本身,由於小圓圈是有陰影的,在 Z 軸它不該該被覆蓋的。並且進來那一瞬間,也的確看到小圓圈 loading 而後才消失。源碼分析

那就是觸摸事件沒有傳遞到的緣由咯。爲何在沒有數據時事件就會有問題呢?看來這個問題仍是要回到 RecyclerView 自己。接着我打開 RecyclerView 的源碼,這裏簡單提一下,SwipeRefreshlayoutRecyclerView 都實現了 NestedScroll ,默認你們都比較瞭解這一機制。不熟悉的話,闊以先看看我以前寫的 Android 打造專屬的下拉刷新 加載更多 佈局

其實這篇文章講的就是上篇文章忽略的那幾行代碼。在 RecyclerView 開始消費事件後,RecyclerView 最終調用了 scrollByInternal() 開始消費事件。接着看看這個方法的詳細代碼。spa

boolean scrollByInternal(int x, int y, MotionEvent ev) {
    int unconsumedX = 0, unconsumedY = 0;
    int consumedX = 0, consumedY = 0;

    consumePendingUpdateOperations();
    if (mAdapter != null) {
        ...
        if (x != 0) {
            consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
            unconsumedX = x - consumedX;
        }
        if (y != 0) {
            consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
            unconsumedY = y - consumedY;
        }
        ...
    }
    ...
    if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
            TYPE_TOUCH)) {
        ...
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];
    }
複製代碼

若是 Adapter 爲空的話,那麼 unconsumedX、Y consumedX、Y 都是默認值 0 ,接着再看看 dispatchNestedScroll() 這個方法,最後會到 NestedScrollingChildHelper 方法中:code

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
        int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,
        @NestedScrollType int type) {
    if (isNestedScrollingEnabled()) {
        final ViewParent parent = getNestedScrollingParentForType(type);
        if (parent == null) {
            return false;
        }

        if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
            int startX = 0;
            int startY = 0;
            if (offsetInWindow != null) {
                mView.getLocationInWindow(offsetInWindow);
                startX = offsetInWindow[0];
                startY = offsetInWindow[1];
            }

            ViewParentCompat.onNestedScroll(parent, mView, dxConsumed,
                    dyConsumed, dxUnconsumed, dyUnconsumed, type);

            if (offsetInWindow != null) {
                mView.getLocationInWindow(offsetInWindow);
                offsetInWindow[0] -= startX;
                offsetInWindow[1] -= startY;
            }
            return true;
        } else if (offsetInWindow != null) {
            // No motion, no dispatch. Keep offsetInWindow up to date.
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
}
複製代碼

那由於默認值都是0 ,因此這個方法直接返回false,內部事件

ViewParentCompat.onNestedScroll(parent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
複製代碼

確定走不到,天然而然,Parent 確定收不到相關事件。這裏的 Parent 就是 SwipeRefreshLayout , 對應的就是我提到的那個現象,無法執行下拉刷新的操做。ip

結合剛剛分析,發現事件能傳遞給 parent 有一個前置條件就是必定要設置過 Adapter,這個就想使用 RecyclerView 必定要先設置 LayoutManager 同樣get

再回到項目中看看具體代碼,才發現同事寫的代碼裏 Adapter 是懶加載的形式,且數據集合是經過構造函數傳遞進去的,那麼問題就清晰了。由於沒有數據,因此沒有初始化 Adapter,接着就是分析代碼中由於沒有 Adapter,因此 NestedScroll 這一套都失效了。由於我本身的習慣都是先建立出 AdapterLayoutManager 數據都是動態添加,因此之前沒有出現這個狀況,還有,之前空白頁都是作到 RecyclerView 內部,這樣更沒機會出現這個狀況。源碼

因此題目並非最準確的描述,而應該描述爲 SwipeRefreshLayoutRecyclerView 沒有設置 Adapter 時下拉失效。一次簡單快速的源碼分析以及問題定位。io

相關文章
相關標籤/搜索