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

相关文章
相关标签/搜索