为一个旧页面添加空白页后验证时出现了意想不到的情况,SwipeRefreshLayout
在 RecyclerView
空白时下拉失效。布局是 SwipeRefreshLayout
里面有一个 RecyclerView
还有一个 ViewStub
用来显示空白页面。出现情况就是当 RecyclerView
没有数据,展示出空白页之后,SwipeRefreshLayout
就无法下拉。
一开始以为是空白页挡住 SwipeRefreshLayout
,后面迅速又否定来自己,因为小圆圈是有阴影的,在 Z 轴它不应该被覆盖的。而且进来那一瞬间,也的确看到小圆圈 loading 然后才消失。
那就是触摸事件没有传递到的原因咯。为什么在没有数据时事件就会有问题呢?看来这个问题还是要回到 RecyclerView 本身。接着我打开 RecyclerView
的源码,这里简单提一下,SwipeRefreshlayout
和 RecyclerView
都实现了 NestedScroll
,默认大家都比较了解这一机制。不熟悉的话,阔以先看看我之前写的
其实这篇文章讲的就是上篇文章忽略的那几行代码。在 RecyclerView
开始消费事件后,RecyclerView
最终调用了 scrollByInternal()
开始消费事件。接着看看这个方法的详细代码。
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
方法中:
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
, 对应的就是我提到的那个现象,没法执行下拉刷新的操作。
结合刚刚分析,发现事件能传递给 parent
有一个前置条件就是一定要设置过 Adapter
,这个就想使用 RecyclerView
一定要先设置 LayoutManager
一样。
再回到项目中看看具体代码,才发现同事写的代码里 Adapter
是懒加载的形式,且数据集合是通过构造函数传递进去的,那么问题就清晰了。因为没有数据,所以没有初始化 Adapter
,接着就是分析代码中因为没有 Adapter
,所以 NestedScroll
这一套都失效了。因为我自己的习惯都是先创建出 Adapter
和 LayoutManager
数据都是动态添加,所以以前没有出现这个情况,还有,以前空白页都是做到 RecyclerView
内部,这样更没机会出现这个情况。
所以题目并不是最准确的描述,而应该描述为 SwipeRefreshLayout
在 RecyclerView
没有设置 Adapter
时下拉失效。一次简单快速的源码分析以及问题定位。