Fix StaggaredGridLayoutManager prefetch crash
Fixes: 33654151
Test: @SmallTests pass
StaggaredGridLayoutManager prefetching would attempt to prefetch in
the wrong direction (and pass a negative distance) if a span had no
content left.
Change-Id: Ia1d8f34436d436b72e37e0058c94c2ccdf51c6ca
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index adbe4dd..19f31f8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -2097,15 +2097,23 @@
if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
mPrefetchDistances = new int[mSpanCount];
}
+
+ int itemPrefetchCount = 0;
for (int i = 0; i < mSpanCount; i++) {
- mPrefetchDistances[i] = mLayoutState.mItemDirection == LAYOUT_START
+ // compute number of pixels past the edge of the viewport that the current span extends
+ int distance = mLayoutState.mItemDirection == LAYOUT_START
? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
: mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
+ if (distance >= 0) {
+ // span extends to the edge, so prefetch next item
+ mPrefetchDistances[itemPrefetchCount] = distance;
+ itemPrefetchCount++;
+ }
}
- Arrays.sort(mPrefetchDistances, 0, mSpanCount);
+ Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
// then assign them in order to the next N views (where N = span count)
- for (int i = 0; i < mSpanCount && mLayoutState.hasMore(state); i++) {
+ for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index cc39a87..7ab616f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -515,6 +515,50 @@
}
@Test
+ public void prefetchStaggeredPastBoundary() {
+ StaggeredGridLayoutManager sglm =
+ new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+ mRecyclerView.setLayoutManager(sglm);
+ mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new RecyclerView.ViewHolder(new View(getContext())) {};
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ holder.itemView.setMinimumWidth(100);
+ holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
+ }
+
+ @Override
+ public int getItemCount() {
+ return 2;
+ }
+ });
+
+ layout(200, 100);
+ mRecyclerView.scrollBy(0, 50);
+
+ /* Each row is 50 pixels:
+ * ------------- *
+ *___0___|___1___*
+ * 0 | 1 *
+ *_______|___1___*
+ * | 1 *
+ */
+ assertEquals(2, mRecyclerView.getChildCount());
+ assertEquals(0, sglm.getFirstChildPosition());
+ assertEquals(1, sglm.getLastChildPosition());
+
+ // prefetch upward gets nothing
+ CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
+
+ // prefetch downward gets nothing (and doesn't crash...)
+ CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
+ }
+
+ @Test
public void prefetchItemsSkipAnimations() {
LinearLayoutManager llm = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(llm);