StaggeredGridLayoutManagerTestpublic class StaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest
Fields Summary |
---|
private static final boolean | DEBUG | private static final String | TAG | volatile WrappedLayoutManager | mLayoutManager | GridTestAdapter | mAdapter | final List | mBaseVariations |
Methods Summary |
---|
public void | assertRectSetsEqual(java.lang.String message, java.util.Map before, java.util.Map after)
StringBuilder log = new StringBuilder();
if (DEBUG) {
log.append("checking rectangle equality.\n");
log.append("before:");
for (Map.Entry<Item, Rect> entry : before.entrySet()) {
log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
.append(entry.getValue());
}
log.append("\nafter:");
for (Map.Entry<Item, Rect> entry : after.entrySet()) {
log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
.append(entry.getValue());
}
message += "\n\n" + log.toString();
}
assertEquals(message + ": item counts should be equal", before.size()
, after.size());
for (Map.Entry<Item, Rect> entry : before.entrySet()) {
Rect afterRect = after.get(entry.getKey());
assertNotNull(message + ": Same item should be visible after simple re-layout",
afterRect);
assertEquals(message + ": Item should be laid out at the same coordinates",
entry.getValue(),
afterRect);
}
| public void | assertRectSetsNotEqual(java.lang.String message, java.util.Map before, java.util.Map after)
Throwable throwable = null;
try {
assertRectSetsEqual("NOT " + message, before, after);
} catch (Throwable t) {
throwable = t;
}
assertNotNull(message + " two layout should be different", throwable);
| void | assertSpan(java.lang.String msg, int childPosition, int expectedSpan)
View view = mLayoutManager.findViewByPosition(childPosition);
assertNotNull(msg + "view at position " + childPosition + " should exists", view);
assertEquals(msg + "[child:" + childPosition + "]", expectedSpan,
getLp(view).mSpan.mIndex);
| void | assertSpanAssignmentEquality(java.lang.String msg, int[] set1, int[] set2, int start, int end)
for (int i = start; i < end; i++) {
assertEquals(msg + " ind:" + i, set1[i], set2[i]);
}
| void | assertSpanAssignmentEquality(java.lang.String msg, int[] set1, int[] set2, int start1, int start2, int length)
for (int i = 0; i < length; i++) {
assertEquals(msg + " ind1:" + (start1 + i) + ", ind2:" + (start2 + i), set1[start1 + i],
set2[start2 + i]);
}
| void | assertSpans(java.lang.String msg, int[] childSpanTuples)
for (int i = 0; i < childSpanTuples.length; i++) {
assertSpan(msg, childSpanTuples[i][0], childSpanTuples[i][1]);
}
| void | assertViewPositions(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
ArrayList<ArrayList<View>> viewsBySpan = mLayoutManager.collectChildrenBySpan();
OrientationHelper orientationHelper = OrientationHelper
.createOrientationHelper(mLayoutManager, config.mOrientation);
for (ArrayList<View> span : viewsBySpan) {
// validate all children's order. first child should have min start mPosition
final int count = span.size();
for (int i = 0, j = 1; j < count; i++, j++) {
View prev = span.get(i);
View next = span.get(j);
assertTrue(config + " prev item should be above next item",
orientationHelper.getDecoratedEnd(prev) <= orientationHelper
.getDecoratedStart(next)
);
}
}
| public void | consistentRelayoutTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config, boolean firstChildMultiSpan)
setupByConfig(config);
if (firstChildMultiSpan) {
mAdapter.mFullSpanItems.add(0);
}
waitFirstLayout();
// record all child positions
Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
requestLayoutOnUIThread(mRecyclerView);
Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
assertRectSetsEqual(
config + " simple re-layout, firstChildMultiSpan:" + firstChildMultiSpan, before,
after);
// scroll some to create inconsistency
View firstChild = mLayoutManager.getChildAt(0);
final int firstChildStartBeforeScroll = mLayoutManager.mPrimaryOrientation
.getDecoratedStart(firstChild);
int distance = mLayoutManager.mPrimaryOrientation.getDecoratedMeasurement(firstChild) / 2;
if (config.mReverseLayout) {
distance *= -1;
}
scrollBy(distance);
waitForMainThread(2);
assertTrue("scroll by should move children", firstChildStartBeforeScroll !=
mLayoutManager.mPrimaryOrientation.getDecoratedStart(firstChild));
before = mLayoutManager.collectChildCoordinates();
mLayoutManager.expectLayouts(1);
requestLayoutOnUIThread(mRecyclerView);
mLayoutManager.waitForLayout(2);
after = mLayoutManager.collectChildCoordinates();
assertRectSetsEqual(config + " simple re-layout after scroll", before, after);
| private int[] | copyOfRange(int[] original, int from, int to)
int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
int[] copy = new int[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
| public void | customSizeInScrollDirectionTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
setupByConfig(config);
final Map<View, Integer> sizeMap = new HashMap<View, Integer>();
mAdapter.mOnBindHandler = new OnBindHandler() {
@Override
void onBoundItem(TestViewHolder vh, int position) {
final ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams();
final int size = 1 + position * 5;
if (config.mOrientation == HORIZONTAL) {
layoutParams.width = size;
} else {
layoutParams.height = size;
}
sizeMap.put(vh.itemView, size);
if (position == 3) {
getLp(vh.itemView).setFullSpan(true);
}
}
@Override
boolean assignRandomSize() {
return false;
}
};
waitFirstLayout();
assertTrue("[test sanity] some views should be laid out", sizeMap.size() > 0);
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
: child.getHeight();
assertEquals("child " + i + " should have the size specified in its layout params",
sizeMap.get(child).intValue(), size);
}
checkForMainThreadException();
| private TargetTuple | findInvisibleTarget(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
View child = mLayoutManager.getChildAt(i);
int position = mRecyclerView.getChildLayoutPosition(child);
if (position < minPosition) {
minPosition = position;
}
if (position > maxPosition) {
maxPosition = position;
}
}
final int tailTarget = maxPosition + (mAdapter.getItemCount() - maxPosition) / 2;
final int headTarget = minPosition / 2;
final int target;
// where will the child come from ?
final int itemLayoutDirection;
if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
target = tailTarget;
itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
} else {
target = headTarget;
itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
}
if (DEBUG) {
Log.d(TAG,
config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
}
return new TargetTuple(target, itemLayoutDirection);
| public void | gapAtTheBeginningOfTheListTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config, int deletePosition, int deleteCount)
if (config.mSpanCount < 2 || config.mGapStrategy == GAP_HANDLING_NONE) {
return;
}
if (config.mItemCount < 100) {
config.itemCount(100);
}
final String logPrefix = config + ", deletePos:" + deletePosition + ", deleteCount:"
+ deleteCount;
setupByConfig(config);
final RecyclerView.Adapter adapter = mAdapter;
waitFirstLayout();
// scroll far away
smoothScrollToPosition(config.mItemCount / 2);
// assert to be deleted child is not visible
assertNull(logPrefix + " test sanity, to be deleted child should be invisible",
mRecyclerView.findViewHolderForLayoutPosition(deletePosition));
// delete the child and notify
mAdapter.deleteAndNotify(deletePosition, deleteCount);
getInstrumentation().waitForIdleSync();
mLayoutManager.expectLayouts(1);
smoothScrollToPosition(0);
mLayoutManager.waitForLayout(2);
// due to data changes, first item may become visible before others which will cause
// smooth scrolling to stop. Triggering it twice more is a naive hack.
// Until we have time to consider it as a bug, this is the only workaround.
smoothScrollToPosition(0);
Thread.sleep(300);
smoothScrollToPosition(0);
Thread.sleep(500);
// some animations should happen and we should recover layout
final Map<Item, Rect> actualCoords = mLayoutManager.collectChildCoordinates();
// now layout another RV with same adapter
removeRecyclerView();
setupByConfig(config);
mRecyclerView.setAdapter(adapter);// use same adapter so that items can be matched
waitFirstLayout();
final Map<Item, Rect> desiredCoords = mLayoutManager.collectChildCoordinates();
assertRectSetsEqual(logPrefix + " when an item from the start of the list is deleted, "
+ "layout should recover the state once scrolling is stopped",
desiredCoords, actualCoords);
| public void | gapInTheMiddle(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
| public void | getFirstLastChildrenTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config, boolean provideArr)
setupByConfig(config);
waitFirstLayout();
Runnable viewInBoundsTest = new Runnable() {
@Override
public void run() {
VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
final String boundsLog = mLayoutManager.getBoundsLog();
VisibleChildren queryResult = new VisibleChildren(mLayoutManager.getSpanCount());
queryResult.firstFullyVisiblePositions = mLayoutManager
.findFirstCompletelyVisibleItemPositions(
provideArr ? new int[mLayoutManager.getSpanCount()] : null);
queryResult.firstVisiblePositions = mLayoutManager
.findFirstVisibleItemPositions(
provideArr ? new int[mLayoutManager.getSpanCount()] : null);
queryResult.lastFullyVisiblePositions = mLayoutManager
.findLastCompletelyVisibleItemPositions(
provideArr ? new int[mLayoutManager.getSpanCount()] : null);
queryResult.lastVisiblePositions = mLayoutManager
.findLastVisibleItemPositions(
provideArr ? new int[mLayoutManager.getSpanCount()] : null);
assertEquals(config + ":\nfirst visible child should match traversal result\n"
+ "traversed:" + visibleChildren + "\n"
+ "queried:" + queryResult + "\n"
+ boundsLog, visibleChildren, queryResult
);
}
};
runTestOnUiThread(viewInBoundsTest);
// smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
// case
final int scrollPosition = mAdapter.getItemCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.smoothScrollToPosition(scrollPosition);
}
});
while (mLayoutManager.isSmoothScrolling() ||
mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
runTestOnUiThread(viewInBoundsTest);
checkForMainThreadException();
Thread.sleep(400);
}
// delete all items
mLayoutManager.expectLayouts(2);
mAdapter.deleteAndNotify(0, mAdapter.getItemCount());
mLayoutManager.waitForLayout(2);
// test empty case
runTestOnUiThread(viewInBoundsTest);
// set a new adapter with huge items to test full bounds check
mLayoutManager.expectLayouts(1);
final int totalSpace = mLayoutManager.mPrimaryOrientation.getTotalSpace();
final TestAdapter newAdapter = new TestAdapter(100) {
@Override
public void onBindViewHolder(TestViewHolder holder,
int position) {
super.onBindViewHolder(holder, position);
if (config.mOrientation == LinearLayoutManager.HORIZONTAL) {
holder.itemView.setMinimumWidth(totalSpace + 5);
} else {
holder.itemView.setMinimumHeight(totalSpace + 5);
}
}
};
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.setAdapter(newAdapter);
}
});
mLayoutManager.waitForLayout(2);
runTestOnUiThread(viewInBoundsTest);
checkForMainThreadException();
| LayoutParams | getLp(android.view.View view)
return (LayoutParams) view.getLayoutParams();
| public void | innerGapHandlingTest(int strategy)
Config config = new Config().spanCount(3).itemCount(500);
setupByConfig(config);
mLayoutManager.setGapStrategy(strategy);
mAdapter.mFullSpanItems.add(100);
mAdapter.mFullSpanItems.add(104);
mAdapter.mViewsHaveEqualSize = true;
waitFirstLayout();
mLayoutManager.expectLayouts(1);
scrollToPosition(400);
mLayoutManager.waitForLayout(2);
mLayoutManager.expectLayouts(2);
mAdapter.addAndNotify(101, 1);
mLayoutManager.waitForLayout(2);
if (strategy == GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
mLayoutManager.expectLayouts(1);
}
// state
// now smooth scroll to 99 to trigger a layout around 100
smoothScrollToPosition(99);
switch (strategy) {
case GAP_HANDLING_NONE:
assertSpans("gap handling:" + Config.gapStrategyName(strategy), new int[]{100, 0},
new int[]{101, 2}, new int[]{102, 0}, new int[]{103, 1}, new int[]{104, 2},
new int[]{105, 0});
break;
case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
mLayoutManager.waitForLayout(2);
assertSpans("swap items between spans", new int[]{100, 0}, new int[]{101, 0},
new int[]{102, 1}, new int[]{103, 2}, new int[]{104, 0}, new int[]{105, 0});
break;
}
| public void | layoutOrderTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
setupByConfig(config);
assertViewPositions(config);
| void | rtlTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config, boolean changeRtlAfter)
if (config.mSpanCount == 1) {
config.mSpanCount = 2;
}
String logPrefix = config + ", changeRtlAfterLayout:" + changeRtlAfter;
setupByConfig(config.itemCount(5));
if (changeRtlAfter) {
waitFirstLayout();
mLayoutManager.expectLayouts(1);
mLayoutManager.setFakeRtl(true);
mLayoutManager.waitForLayout(2);
} else {
mLayoutManager.mFakeRTL = true;
waitFirstLayout();
}
assertEquals("view should become rtl", true, mLayoutManager.isLayoutRTL());
OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
View child0 = mLayoutManager.findViewByPosition(0);
View child1 = mLayoutManager.findViewByPosition(config.mOrientation == VERTICAL ? 1
: config.mSpanCount);
assertNotNull(logPrefix + " child position 0 should be laid out", child0);
assertNotNull(logPrefix + " child position 0 should be laid out", child1);
if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
assertTrue(logPrefix + " second child should be to the left of first child",
helper.getDecoratedStart(child0) >= helper.getDecoratedEnd(child1));
assertEquals(logPrefix + " first child should be right aligned",
helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
} else {
assertTrue(logPrefix + " first child should be to the left of second child",
helper.getDecoratedStart(child1) >= helper.getDecoratedEnd(child0));
assertEquals(logPrefix + " first child should be left aligned",
helper.getDecoratedStart(child0), helper.getStartAfterPadding());
}
checkForMainThreadException();
| private void | saveRestore(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
runTestOnUiThread(new Runnable() {
@Override
public void run() {
try {
Parcelable savedState = mRecyclerView.onSaveInstanceState();
// we append a suffix to the parcelable to test out of bounds
String parcelSuffix = UUID.randomUUID().toString();
Parcel parcel = Parcel.obtain();
savedState.writeToParcel(parcel, 0);
parcel.writeString(parcelSuffix);
removeRecyclerView();
// reset for reading
parcel.setDataPosition(0);
// re-create
savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
RecyclerView restored = new RecyclerView(getActivity());
mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
config.mOrientation);
mLayoutManager.setGapStrategy(config.mGapStrategy);
restored.setLayoutManager(mLayoutManager);
// use the same adapter for Rect matching
restored.setAdapter(mAdapter);
restored.onRestoreInstanceState(savedState);
if (Looper.myLooper() == Looper.getMainLooper()) {
mLayoutManager.expectLayouts(1);
setRecyclerView(restored);
} else {
mLayoutManager.expectLayouts(1);
setRecyclerView(restored);
mLayoutManager.waitForLayout(2);
}
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
}
});
checkForMainThreadException();
| public void | savedStateTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config, boolean waitForLayout, android.support.v7.widget.StaggeredGridLayoutManagerTest$PostLayoutRunnable postLayoutOperations)
if (DEBUG) {
Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config "
+ config + " post layout action " + postLayoutOperations.describe());
}
setupByConfig(config);
waitFirstLayout();
if (waitForLayout) {
postLayoutOperations.run();
}
final int firstCompletelyVisiblePosition = mLayoutManager.findFirstVisibleItemPositionInt();
Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
Parcelable savedState = mRecyclerView.onSaveInstanceState();
// we append a suffix to the parcelable to test out of bounds
String parcelSuffix = UUID.randomUUID().toString();
Parcel parcel = Parcel.obtain();
savedState.writeToParcel(parcel, 0);
parcel.writeString(parcelSuffix);
removeRecyclerView();
// reset for reading
parcel.setDataPosition(0);
// re-create
savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
removeRecyclerView();
RecyclerView restored = new RecyclerView(getActivity());
mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
mLayoutManager.setGapStrategy(config.mGapStrategy);
restored.setLayoutManager(mLayoutManager);
// use the same adapter for Rect matching
restored.setAdapter(mAdapter);
restored.onRestoreInstanceState(savedState);
assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
parcel.readString());
mLayoutManager.expectLayouts(1);
setRecyclerView(restored);
mLayoutManager.waitForLayout(2);
assertEquals(config + " on saved state, reverse layout should be preserved",
config.mReverseLayout, mLayoutManager.getReverseLayout());
assertEquals(config + " on saved state, orientation should be preserved",
config.mOrientation, mLayoutManager.getOrientation());
assertEquals(config + " on saved state, span count should be preserved",
config.mSpanCount, mLayoutManager.getSpanCount());
assertEquals(config + " on saved state, gap strategy should be preserved",
config.mGapStrategy, mLayoutManager.getGapStrategy());
assertEquals(config + " on saved state, first completely visible child position should"
+ " be preserved", firstCompletelyVisiblePosition,
mLayoutManager.findFirstVisibleItemPositionInt());
if (waitForLayout) {
assertRectSetsEqual(config + "\npost layout op:" + postLayoutOperations.describe()
+ ": on restore, previous view positions should be preserved",
before, mLayoutManager.collectChildCoordinates()
);
}
// TODO add tests for changing values after restore before layout
| public void | scrollBackAndPreservePositionsTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config, boolean saveRestoreInBetween)
setupByConfig(config);
mAdapter.mOnBindHandler = new OnBindHandler() {
@Override
public void onBoundItem(TestViewHolder vh, int position) {
LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
lp.setFullSpan((position * 7) % (config.mSpanCount + 1) == 0);
}
};
waitFirstLayout();
final int[] globalPositions = new int[mAdapter.getItemCount()];
Arrays.fill(globalPositions, Integer.MIN_VALUE);
final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10)
* (config.mReverseLayout ? -1 : 1);
final int[] globalPos = new int[1];
runTestOnUiThread(new Runnable() {
@Override
public void run() {
int globalScrollPosition = 0;
while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
final int pos = mRecyclerView.getChildLayoutPosition(child);
if (globalPositions[pos] != Integer.MIN_VALUE) {
continue;
}
if (config.mReverseLayout) {
globalPositions[pos] = globalScrollPosition +
mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
} else {
globalPositions[pos] = globalScrollPosition +
mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
}
}
globalScrollPosition += mLayoutManager.scrollBy(scrollStep,
mRecyclerView.mRecycler, mRecyclerView.mState);
}
if (DEBUG) {
Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
}
globalPos[0] = globalScrollPosition;
}
});
checkForMainThreadException();
if (saveRestoreInBetween) {
saveRestore(config);
}
checkForMainThreadException();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
int globalScrollPosition = globalPos[0];
// now scroll back and make sure global positions match
BitSet shouldTest = new BitSet(mAdapter.getItemCount());
shouldTest.set(0, mAdapter.getItemCount() - 1, true);
String assertPrefix = config + ", restored in between:" + saveRestoreInBetween
+ " global pos must match when scrolling in reverse for position ";
int scrollAmount = Integer.MAX_VALUE;
while (!shouldTest.isEmpty() && scrollAmount != 0) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
View child = mRecyclerView.getChildAt(i);
int pos = mRecyclerView.getChildLayoutPosition(child);
if (!shouldTest.get(pos)) {
continue;
}
shouldTest.clear(pos);
int globalPos;
if (config.mReverseLayout) {
globalPos = globalScrollPosition +
mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
} else {
globalPos = globalScrollPosition +
mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
}
assertEquals(assertPrefix + pos,
globalPositions[pos], globalPos);
}
scrollAmount = mLayoutManager.scrollBy(-scrollStep,
mRecyclerView.mRecycler, mRecyclerView.mState);
globalScrollPosition += scrollAmount;
}
assertTrue("all views should be seen", shouldTest.isEmpty());
}
});
checkForMainThreadException();
| public void | scrollByTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
setupByConfig(config);
waitFirstLayout();
// try invalid scroll. should not happen
final View first = mLayoutManager.getChildAt(0);
OrientationHelper primaryOrientation = OrientationHelper
.createOrientationHelper(mLayoutManager, config.mOrientation);
int scrollDist;
if (config.mReverseLayout) {
scrollDist = primaryOrientation.getDecoratedMeasurement(first) / 2;
} else {
scrollDist = -primaryOrientation.getDecoratedMeasurement(first) / 2;
}
Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
scrollBy(scrollDist);
Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
assertRectSetsEqual(
config + " if there are no more items, scroll should not happen (dt:" + scrollDist
+ ")",
before, after
);
scrollDist = -scrollDist * 3;
before = mLayoutManager.collectChildCoordinates();
scrollBy(scrollDist);
after = mLayoutManager.collectChildCoordinates();
int layoutStart = primaryOrientation.getStartAfterPadding();
int layoutEnd = primaryOrientation.getEndAfterPadding();
for (Map.Entry<Item, Rect> entry : before.entrySet()) {
Rect afterRect = after.get(entry.getKey());
// offset rect
if (config.mOrientation == VERTICAL) {
entry.getValue().offset(0, -scrollDist);
} else {
entry.getValue().offset(-scrollDist, 0);
}
if (afterRect == null || afterRect.isEmpty()) {
// assert item is out of bounds
int start, end;
if (config.mOrientation == VERTICAL) {
start = entry.getValue().top;
end = entry.getValue().bottom;
} else {
start = entry.getValue().left;
end = entry.getValue().right;
}
assertTrue(
config + " if item is missing after relayout, it should be out of bounds."
+ "item start: " + start + ", end:" + end + " layout start:"
+ layoutStart +
", layout end:" + layoutEnd,
start <= layoutStart && end <= layoutEnd ||
start >= layoutEnd && end >= layoutEnd
);
} else {
assertEquals(config + " Item should be laid out at the scroll offset coordinates",
entry.getValue(),
afterRect);
}
}
assertViewPositions(config);
| public void | scrollToPositionTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
setupByConfig(config);
waitFirstLayout();
OrientationHelper orientationHelper = OrientationHelper
.createOrientationHelper(mLayoutManager, config.mOrientation);
Rect layoutBounds = getDecoratedRecyclerViewBounds();
for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
View view = mLayoutManager.getChildAt(i);
Rect bounds = mLayoutManager.getViewBounds(view);
if (layoutBounds.contains(bounds)) {
Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates();
final int position = mRecyclerView.getChildLayoutPosition(view);
LayoutParams layoutParams
= (LayoutParams) (view.getLayoutParams());
TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder;
assertEquals("recycler view mPosition should match adapter mPosition", position,
vh.mBoundItem.mAdapterIndex);
if (DEBUG) {
Log.d(TAG, "testing scroll to visible mPosition at " + position
+ " " + bounds + " inside " + layoutBounds);
}
mLayoutManager.expectLayouts(1);
scrollToPosition(position);
mLayoutManager.waitForLayout(2);
if (DEBUG) {
view = mLayoutManager.findViewByPosition(position);
Rect newBounds = mLayoutManager.getViewBounds(view);
Log.d(TAG, "after scrolling to visible mPosition " +
bounds + " equals " + newBounds);
}
assertRectSetsEqual(
config + "scroll to mPosition on fully visible child should be no-op",
initialBounds, mLayoutManager.collectChildCoordinates());
} else {
final int position = mRecyclerView.getChildLayoutPosition(view);
if (DEBUG) {
Log.d(TAG,
"child(" + position + ") not fully visible " + bounds + " not inside "
+ layoutBounds
+ mRecyclerView.getChildLayoutPosition(view)
);
}
mLayoutManager.expectLayouts(1);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mLayoutManager.scrollToPosition(position);
}
});
mLayoutManager.waitForLayout(2);
view = mLayoutManager.findViewByPosition(position);
bounds = mLayoutManager.getViewBounds(view);
if (DEBUG) {
Log.d(TAG, "after scroll to partially visible child " + bounds + " in "
+ layoutBounds);
}
assertTrue(config
+ " after scrolling to a partially visible child, it should become fully "
+ " visible. " + bounds + " not inside " + layoutBounds,
layoutBounds.contains(bounds)
);
assertTrue(config + " when scrolling to a partially visible item, one of its edges "
+ "should be on the boundaries", orientationHelper.getStartAfterPadding() ==
orientationHelper.getDecoratedStart(view)
|| orientationHelper.getEndAfterPadding() ==
orientationHelper.getDecoratedEnd(view));
}
}
// try scrolling to invisible children
int testCount = 10;
while (testCount-- > 0) {
final TargetTuple target = findInvisibleTarget(config);
mLayoutManager.expectLayouts(1);
scrollToPosition(target.mPosition);
mLayoutManager.waitForLayout(2);
final View child = mLayoutManager.findViewByPosition(target.mPosition);
assertNotNull(config + " scrolling to a mPosition should lay it out", child);
final Rect bounds = mLayoutManager.getViewBounds(child);
if (DEBUG) {
Log.d(TAG, config + " post scroll to invisible mPosition " + bounds + " in "
+ layoutBounds);
}
assertTrue(config + " scrolling to a mPosition should make it fully visible",
layoutBounds.contains(bounds));
if (target.mLayoutDirection == LAYOUT_START) {
assertEquals(
config + " when scrolling to an invisible child above, its start should"
+ " align with recycler view's start",
orientationHelper.getStartAfterPadding(),
orientationHelper.getDecoratedStart(child)
);
} else {
assertEquals(config + " when scrolling to an invisible child below, its end "
+ "should align with recycler view's end",
orientationHelper.getEndAfterPadding(),
orientationHelper.getDecoratedEnd(child)
);
}
}
| private void | scrollToPositionWithOffset(int position, int offset)
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mLayoutManager.scrollToPositionWithOffset(position, offset);
}
});
| public void | scrollToPositionWithOffsetTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
setupByConfig(config);
waitFirstLayout();
OrientationHelper orientationHelper = OrientationHelper
.createOrientationHelper(mLayoutManager, config.mOrientation);
Rect layoutBounds = getDecoratedRecyclerViewBounds();
// try scrolling towards head, should not affect anything
Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
scrollToPositionWithOffset(0, 20);
assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
before, mLayoutManager.collectChildCoordinates());
// try offsetting some visible children
int testCount = 10;
while (testCount-- > 0) {
// get middle child
final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
final int position = mRecyclerView.getChildLayoutPosition(child);
final int startOffset = config.mReverseLayout ?
orientationHelper.getEndAfterPadding() - orientationHelper
.getDecoratedEnd(child)
: orientationHelper.getDecoratedStart(child) - orientationHelper
.getStartAfterPadding();
final int scrollOffset = startOffset / 2;
mLayoutManager.expectLayouts(1);
scrollToPositionWithOffset(position, scrollOffset);
mLayoutManager.waitForLayout(2);
final int finalOffset = config.mReverseLayout ?
orientationHelper.getEndAfterPadding() - orientationHelper
.getDecoratedEnd(child)
: orientationHelper.getDecoratedStart(child) - orientationHelper
.getStartAfterPadding();
assertEquals(config + " scroll with offset on a visible child should work fine",
scrollOffset, finalOffset);
}
// try scrolling to invisible children
testCount = 10;
// we test above and below, one by one
int offsetMultiplier = -1;
while (testCount-- > 0) {
final TargetTuple target = findInvisibleTarget(config);
mLayoutManager.expectLayouts(1);
final int offset = offsetMultiplier
* orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
scrollToPositionWithOffset(target.mPosition, offset);
mLayoutManager.waitForLayout(2);
final View child = mLayoutManager.findViewByPosition(target.mPosition);
assertNotNull(config + " scrolling to a mPosition with offset " + offset
+ " should layout it", child);
final Rect bounds = mLayoutManager.getViewBounds(child);
if (DEBUG) {
Log.d(TAG, config + " post scroll to invisible mPosition " + bounds + " in "
+ layoutBounds + " with offset " + offset);
}
if (config.mReverseLayout) {
assertEquals(config + " when scrolling with offset to an invisible in reverse "
+ "layout, its end should align with recycler view's end - offset",
orientationHelper.getEndAfterPadding() - offset,
orientationHelper.getDecoratedEnd(child)
);
} else {
assertEquals(config + " when scrolling with offset to an invisible child in normal"
+ " layout its start should align with recycler view's start + "
+ "offset",
orientationHelper.getStartAfterPadding() + offset,
orientationHelper.getDecoratedStart(child)
);
}
offsetMultiplier *= -1;
}
| public void | scrollToPositionWithPredictive(int scrollPosition, int scrollOffset)
setupByConfig(new Config(StaggeredGridLayoutManager.VERTICAL,
false, 3, StaggeredGridLayoutManager.GAP_HANDLING_NONE));
waitFirstLayout();
mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
@Override
void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
RecyclerView rv = mLayoutManager.mRecyclerView;
if (state.isPreLayout()) {
assertEquals("pending scroll position should still be pending",
scrollPosition, mLayoutManager.mPendingScrollPosition);
if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
assertEquals("pending scroll position offset should still be pending",
scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
}
} else {
RecyclerView.ViewHolder vh = rv.findViewHolderForLayoutPosition(scrollPosition);
assertNotNull("scroll to position should work", vh);
if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
assertEquals("scroll offset should be applied properly",
mLayoutManager.getPaddingTop() + scrollOffset
+ ((RecyclerView.LayoutParams) vh.itemView
.getLayoutParams()).topMargin,
mLayoutManager.getDecoratedTop(vh.itemView));
}
}
}
};
mLayoutManager.expectLayouts(2);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
try {
mAdapter.addAndNotify(0, 1);
if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
mLayoutManager.scrollToPosition(scrollPosition);
} else {
mLayoutManager.scrollToPositionWithOffset(scrollPosition,
scrollOffset);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
});
mLayoutManager.waitForLayout(2);
checkForMainThreadException();
| protected void | setUp()
super.setUp();
for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
for (boolean reverseLayout : new boolean[]{false, true}) {
for (int spanCount : new int[]{1, 3}) {
for (int gapStrategy : new int[]{GAP_HANDLING_NONE,
GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}) {
mBaseVariations.add(new Config(orientation, reverseLayout, spanCount,
gapStrategy));
}
}
}
}
| void | setupByConfig(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
mAdapter = new GridTestAdapter(config.mItemCount, config.mOrientation);
mRecyclerView = new RecyclerView(getActivity());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
config.mOrientation);
mLayoutManager.setGapStrategy(config.mGapStrategy);
mLayoutManager.setReverseLayout(config.mReverseLayout);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
try {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
assertNotNull("view should have layout params assigned", lp);
assertNotNull("when item offsets are requested, view should have a valid span",
lp.mSpan);
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
}
});
| public void | testAccessibilityPositions()
setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE));
waitFirstLayout();
final AccessibilityDelegateCompat delegateCompat = mRecyclerView
.getCompatAccessibilityDelegate();
final AccessibilityEvent event = AccessibilityEvent.obtain();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event);
}
});
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
final int start = mRecyclerView
.getChildLayoutPosition(
mLayoutManager.findFirstVisibleItemClosestToStart(false, true));
final int end = mRecyclerView
.getChildLayoutPosition(
mLayoutManager.findFirstVisibleItemClosestToEnd(false, true));
assertEquals("first item position should match",
Math.min(start, end), record.getFromIndex());
assertEquals("last item position should match",
Math.max(start, end), record.getToIndex());
| public void | testAreAllEndsTheSame()
setupByConfig(new Config(VERTICAL, true, 3, GAP_HANDLING_NONE).itemCount(300));
waitFirstLayout();
smoothScrollToPosition(100);
mLayoutManager.expectLayouts(1);
mAdapter.deleteAndNotify(0, 2);
mLayoutManager.waitForLayout(2);
smoothScrollToPosition(0);
assertFalse("all ends should not be the same", mLayoutManager.areAllEndsEqual());
| public void | testAreAllStartsTheSame()
setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE).itemCount(300));
waitFirstLayout();
smoothScrollToPosition(100);
mLayoutManager.expectLayouts(1);
mAdapter.deleteAndNotify(0, 2);
mLayoutManager.waitForLayout(2);
smoothScrollToPosition(0);
assertFalse("all starts should not be the same", mLayoutManager.areAllStartsEqual());
| public void | testConsistentRelayout()
for (Config config : mBaseVariations) {
for (boolean firstChildMultiSpan : new boolean[]{false, true}) {
consistentRelayoutTest(config, firstChildMultiSpan);
}
removeRecyclerView();
}
| public void | testCustomHeightInVertical()
customSizeInScrollDirectionTest(
new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
| public void | testCustomWidthInHorizontal()
customSizeInScrollDirectionTest(
new Config(HORIZONTAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
| public void | testFindLastInUnevenDistribution()
setupByConfig(new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
.itemCount(5));
mAdapter.mOnBindHandler = new OnBindHandler() {
@Override
void onBoundItem(TestViewHolder vh, int position) {
LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
if (position == 1) {
lp.height = mRecyclerView.getHeight() - 10;
} else {
lp.height = 5;
}
}
};
waitFirstLayout();
int[] into = new int[2];
mLayoutManager.findFirstCompletelyVisibleItemPositions(into);
assertEquals("first completely visible item from span 0 should be 0", 0, into[0]);
assertEquals("first completely visible item from span 1 should be 1", 1, into[1]);
mLayoutManager.findLastCompletelyVisibleItemPositions(into);
assertEquals("last completely visible item from span 0 should be 4", 4, into[0]);
assertEquals("last completely visible item from span 1 should be 1", 1, into[1]);
assertEquals("first fully visible child should be at position",
0, mRecyclerView.getChildViewHolder(mLayoutManager.
findFirstVisibleItemClosestToStart(true, true)).getPosition());
assertEquals("last fully visible child should be at position",
4, mRecyclerView.getChildViewHolder(mLayoutManager.
findFirstVisibleItemClosestToEnd(true, true)).getPosition());
assertEquals("first visible child should be at position",
0, mRecyclerView.getChildViewHolder(mLayoutManager.
findFirstVisibleItemClosestToStart(false, true)).getPosition());
assertEquals("last visible child should be at position",
4, mRecyclerView.getChildViewHolder(mLayoutManager.
findFirstVisibleItemClosestToEnd(false, true)).getPosition());
| public void | testFullSizeSpans()
Config config = new Config().spanCount(5).itemCount(30);
setupByConfig(config);
mAdapter.mFullSpanItems.add(3);
waitFirstLayout();
assertSpans("Testing full size span", new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 2},
new int[]{3, 0}, new int[]{4, 0}, new int[]{5, 1}, new int[]{6, 2},
new int[]{7, 3}, new int[]{8, 4});
| public void | testGapAtTheBeginning()
for (Config config : mBaseVariations) {
for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount++) {
for (int deletePosition = config.mSpanCount - 1;
deletePosition < config.mSpanCount + 2; deletePosition++) {
gapAtTheBeginningOfTheListTest(config, deletePosition, deleteCount);
removeRecyclerView();
}
}
}
| public void | testGetFirstLastChildrenTest()
for (boolean provideArr : new boolean[]{true, false}) {
for (Config config : mBaseVariations) {
getFirstLastChildrenTest(config, provideArr);
removeRecyclerView();
}
}
| public void | testGrowLookup()
setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
waitFirstLayout();
mLayoutManager.expectLayouts(1);
mAdapter.mItems.clear();
mAdapter.dispatchDataSetChanged();
mLayoutManager.waitForLayout(2);
checkForMainThreadException();
mLayoutManager.expectLayouts(2);
mAdapter.addAndNotify(0, 30);
mLayoutManager.waitForLayout(2);
checkForMainThreadException();
| public void | testInnerGapHandling()
innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
| public void | testLayoutOrder()
for (Config config : mBaseVariations) {
layoutOrderTest(config);
removeRecyclerView();
}
| public void | testMoveGapHandling()
Config config = new Config().spanCount(2).itemCount(40);
setupByConfig(config);
waitFirstLayout();
mLayoutManager.expectLayouts(2);
mAdapter.moveAndNotify(4, 1);
mLayoutManager.waitForLayout(2);
assertNull("moving item to upper should not cause gaps", mLayoutManager.hasGapsToFix());
| public void | testPartialSpanInvalidation()
Config config = new Config().spanCount(5).itemCount(100);
setupByConfig(config);
for (int i = 20; i < mAdapter.getItemCount(); i += 20) {
mAdapter.mFullSpanItems.add(i);
}
waitFirstLayout();
smoothScrollToPosition(50);
int prevSpanId = mLayoutManager.mLazySpanLookup.mData[30];
mAdapter.changeAndNotify(15, 2);
Thread.sleep(200);
assertEquals("Invalidation should happen within full span item boundaries", prevSpanId,
mLayoutManager.mLazySpanLookup.mData[30]);
assertEquals("item in invalidated range should have clear span id",
LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]);
smoothScrollToPosition(85);
int[] prevSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 62, 85);
mAdapter.deleteAndNotify(55, 2);
Thread.sleep(200);
assertEquals("item in invalidated range should have clear span id",
LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]);
int[] newSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 60, 83);
assertSpanAssignmentEquality("valid spans should be shifted for deleted item", prevSpans,
newSpans, 0, 0, newSpans.length);
| public void | testRTL()
for (boolean changeRtlAfter : new boolean[]{false, true}) {
for (Config config : mBaseVariations) {
rtlTest(config, changeRtlAfter);
removeRecyclerView();
}
}
| public void | testSavedState()
PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
new PostLayoutRunnable() {
@Override
public void run() throws Throwable {
// do nothing
}
@Override
public String describe() {
return "doing nothing";
}
},
new PostLayoutRunnable() {
@Override
public void run() throws Throwable {
mLayoutManager.expectLayouts(1);
scrollToPosition(mAdapter.getItemCount() * 3 / 4);
mLayoutManager.waitForLayout(2);
}
@Override
public String describe() {
return "scroll to position " + (mAdapter == null ? "" :
mAdapter.getItemCount() * 3 / 4);
}
},
new PostLayoutRunnable() {
@Override
public void run() throws Throwable {
mLayoutManager.expectLayouts(1);
scrollToPositionWithOffset(mAdapter.getItemCount() / 3,
50);
mLayoutManager.waitForLayout(2);
}
@Override
public String describe() {
return "scroll to position " + (mAdapter == null ? "" :
mAdapter.getItemCount() / 3) + "with positive offset";
}
},
new PostLayoutRunnable() {
@Override
public void run() throws Throwable {
mLayoutManager.expectLayouts(1);
scrollToPositionWithOffset(mAdapter.getItemCount() * 2 / 3,
-50);
mLayoutManager.waitForLayout(2);
}
@Override
public String describe() {
return "scroll to position with negative offset";
}
}
};
boolean[] waitForLayoutOptions = new boolean[]{false, true};
List<Config> testVariations = new ArrayList<Config>();
testVariations.addAll(mBaseVariations);
for (Config config : mBaseVariations) {
if (config.mSpanCount < 2) {
continue;
}
final Config clone = (Config) config.clone();
clone.mItemCount = clone.mSpanCount - 1;
testVariations.add(clone);
}
for (Config config : testVariations) {
for (PostLayoutRunnable runnable : postLayoutOptions) {
for (boolean waitForLayout : waitForLayoutOptions) {
savedStateTest(config, waitForLayout, runnable);
removeRecyclerView();
}
}
}
| public void | testScrollBackAndPreservePositions()
for (boolean saveRestore : new boolean[]{false, true}) {
for (Config config : mBaseVariations) {
scrollBackAndPreservePositionsTest(config, saveRestore);
removeRecyclerView();
}
}
| public void | testScrollBy()
for (Config config : mBaseVariations) {
scrollByTest(config);
removeRecyclerView();
}
| public void | testScrollToPosition()
for (Config config : mBaseVariations) {
scrollToPositionTest(config);
removeRecyclerView();
}
| public void | testScrollToPositionWithOffset()
for (Config config : mBaseVariations) {
scrollToPositionWithOffsetTest(config);
removeRecyclerView();
}
| public void | testScrollToPositionWithPredictive()
scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
scrollToPositionWithPredictive(9, 20);
removeRecyclerView();
scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
| public void | testSpanCountChangeOnRestoreSavedState()
Config config = new Config(HORIZONTAL, true, 5, GAP_HANDLING_NONE);
setupByConfig(config);
waitFirstLayout();
int beforeChildCount = mLayoutManager.getChildCount();
Parcelable savedState = mRecyclerView.onSaveInstanceState();
// we append a suffix to the parcelable to test out of bounds
String parcelSuffix = UUID.randomUUID().toString();
Parcel parcel = Parcel.obtain();
savedState.writeToParcel(parcel, 0);
parcel.writeString(parcelSuffix);
removeRecyclerView();
// reset for reading
parcel.setDataPosition(0);
// re-create
savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
removeRecyclerView();
RecyclerView restored = new RecyclerView(getActivity());
mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
mLayoutManager.setReverseLayout(config.mReverseLayout);
mLayoutManager.setGapStrategy(config.mGapStrategy);
restored.setLayoutManager(mLayoutManager);
// use the same adapter for Rect matching
restored.setAdapter(mAdapter);
restored.onRestoreInstanceState(savedState);
mLayoutManager.setSpanCount(1);
mLayoutManager.expectLayouts(1);
setRecyclerView(restored);
mLayoutManager.waitForLayout(2);
assertEquals("on saved state, reverse layout should be preserved",
config.mReverseLayout, mLayoutManager.getReverseLayout());
assertEquals("on saved state, orientation should be preserved",
config.mOrientation, mLayoutManager.getOrientation());
assertEquals("after setting new span count, layout manager should keep new value",
1, mLayoutManager.getSpanCount());
assertEquals("on saved state, gap strategy should be preserved",
config.mGapStrategy, mLayoutManager.getGapStrategy());
assertTrue("when span count is dramatically changed after restore, # of child views "
+ "should change", beforeChildCount > mLayoutManager.getChildCount());
// make sure LLM can layout all children. is some span info is leaked, this would crash
smoothScrollToPosition(mAdapter.getItemCount() - 1);
| public void | testSpanReassignmentsOnItemChange()
Config config = new Config().spanCount(5);
setupByConfig(config);
waitFirstLayout();
smoothScrollToPosition(mAdapter.getItemCount() / 2);
final int changePosition = mAdapter.getItemCount() / 4;
mLayoutManager.expectLayouts(1);
mAdapter.changeAndNotify(changePosition, 1);
mLayoutManager.assertNoLayout("no layout should happen when an invisible child is updated",
1);
// delete an item before visible area
int deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(0)) - 2;
Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
if (DEBUG) {
Log.d(TAG, "before:");
for (Map.Entry<Item, Rect> entry : before.entrySet()) {
Log.d(TAG, entry.getKey().mAdapterIndex + ":" + entry.getValue());
}
}
mLayoutManager.expectLayouts(1);
mAdapter.deleteAndNotify(deletedPosition, 1);
mLayoutManager.waitForLayout(2);
assertRectSetsEqual(config + " when an item towards the head of the list is deleted, it "
+ "should not affect the layout if it is not visible", before,
mLayoutManager.collectChildCoordinates()
);
deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(2));
mLayoutManager.expectLayouts(1);
mAdapter.deleteAndNotify(deletedPosition, 1);
mLayoutManager.waitForLayout(2);
assertRectSetsNotEqual(config + " when a visible item is deleted, it should affect the "
+ "layout", before, mLayoutManager.collectChildCoordinates());
| public void | testTemporaryGapHandling()
int fullSpanIndex = 200;
setupByConfig(new Config().spanCount(2).itemCount(500));
mAdapter.mFullSpanItems.add(fullSpanIndex);
waitFirstLayout();
smoothScrollToPosition(fullSpanIndex + 30);
mLayoutManager.expectLayouts(1);
mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
mLayoutManager.waitForLayout(1);
smoothScrollToPosition(0);
mLayoutManager.expectLayouts(1);
smoothScrollToPosition(fullSpanIndex + 5);
mLayoutManager.assertNoLayout("if an interim gap is fixed, it should not cause a "
+ "relayout", 2);
View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
assertEquals("view 1 span index", 0, lp1.getSpanIndex());
assertEquals("view 2 span index", 1, lp2.getSpanIndex());
assertEquals("no gap between span and view 1",
mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
assertEquals("no gap between span and view 2",
mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
| public void | testUpdateAfterFullSpan()
updateAfterFullSpanGapHandlingTest(0);
| public void | testUpdateAfterFullSpan2()
updateAfterFullSpanGapHandlingTest(20);
| public void | testViewSnapping()
for (Config config : mBaseVariations) {
viewSnapTest(config.itemCount(config.mSpanCount + 1));
removeRecyclerView();
}
| public void | updateAfterFullSpanGapHandlingTest(int fullSpanIndex)
setupByConfig(new Config().spanCount(2).itemCount(100));
mAdapter.mFullSpanItems.add(fullSpanIndex);
waitFirstLayout();
smoothScrollToPosition(fullSpanIndex + 30);
mLayoutManager.expectLayouts(1);
mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
mLayoutManager.waitForLayout(1);
smoothScrollToPosition(fullSpanIndex);
// give it some time to fix the gap
Thread.sleep(500);
View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
assertEquals("view 1 span index", 0, lp1.getSpanIndex());
assertEquals("view 2 span index", 1, lp2.getSpanIndex());
assertEquals("no gap between span and view 1",
mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
assertEquals("no gap between span and view 2",
mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
| public void | viewSnapTest(android.support.v7.widget.StaggeredGridLayoutManagerTest$Config config)
setupByConfig(config);
waitFirstLayout();
// run these tests twice. once initial layout, once after scroll
String logSuffix = "";
for (int i = 0; i < 2; i++) {
Map<Item, Rect> itemRectMap = mLayoutManager.collectChildCoordinates();
Rect recyclerViewBounds = getDecoratedRecyclerViewBounds();
Rect usedLayoutBounds = new Rect();
for (Rect rect : itemRectMap.values()) {
usedLayoutBounds.union(rect);
}
if (DEBUG) {
Log.d(TAG, "testing view snapping (" + logSuffix + ") for config " + config);
}
if (config.mOrientation == VERTICAL) {
assertEquals(config + " there should be no gap on left" + logSuffix,
usedLayoutBounds.left, recyclerViewBounds.left);
assertEquals(config + " there should be no gap on right" + logSuffix,
usedLayoutBounds.right, recyclerViewBounds.right);
if (config.mReverseLayout) {
assertEquals(config + " there should be no gap on bottom" + logSuffix,
usedLayoutBounds.bottom, recyclerViewBounds.bottom);
assertTrue(config + " there should be some gap on top" + logSuffix,
usedLayoutBounds.top > recyclerViewBounds.top);
} else {
assertEquals(config + " there should be no gap on top" + logSuffix,
usedLayoutBounds.top, recyclerViewBounds.top);
assertTrue(config + " there should be some gap at the bottom" + logSuffix,
usedLayoutBounds.bottom < recyclerViewBounds.bottom);
}
} else {
assertEquals(config + " there should be no gap on top" + logSuffix,
usedLayoutBounds.top, recyclerViewBounds.top);
assertEquals(config + " there should be no gap at the bottom" + logSuffix,
usedLayoutBounds.bottom, recyclerViewBounds.bottom);
if (config.mReverseLayout) {
assertEquals(config + " there should be no on right" + logSuffix,
usedLayoutBounds.right, recyclerViewBounds.right);
assertTrue(config + " there should be some gap on left" + logSuffix,
usedLayoutBounds.left > recyclerViewBounds.left);
} else {
assertEquals(config + " there should be no gap on left" + logSuffix,
usedLayoutBounds.left, recyclerViewBounds.left);
assertTrue(config + " there should be some gap on right" + logSuffix,
usedLayoutBounds.right < recyclerViewBounds.right);
}
}
final int scroll = config.mReverseLayout ? -500 : 500;
scrollBy(scroll);
logSuffix = " scrolled " + scroll;
}
| void | waitFirstLayout()
mLayoutManager.expectLayouts(1);
setRecyclerView(mRecyclerView);
mLayoutManager.waitForLayout(2);
getInstrumentation().waitForIdleSync();
| private void | waitForMainThread(int count)enqueues an empty runnable to main thread so that we can be assured it did run
final AtomicInteger i = new AtomicInteger(count);
while (i.get() > 0) {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
i.decrementAndGet();
}
});
}
|
|