LinearLayoutManagerTestpublic class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest Includes tests for {@link LinearLayoutManager}.
Since most UI tests are not practical, these tests are focused on internal data representation
and stability of LinearLayoutManager in response to different events (state change, scrolling
etc) where it is very hard to do manual testing. |
Fields Summary |
---|
private static final boolean | DEBUG | private static final String | TAG | WrappedLinearLayoutManager | mLayoutManager | TestAdapter | mTestAdapter | final List | mBaseVariations |
Methods Summary |
---|
protected java.util.List | addConfigVariation(java.util.List base, java.lang.String fieldName, java.lang.Object variations)
List<Config> newConfigs = new ArrayList<Config>();
Field field = Config.class.getDeclaredField(fieldName);
for (Config config : base) {
for (Object variation : variations) {
Config newConfig = (Config) config.clone();
field.set(newConfig, variation);
newConfigs.add(newConfig);
}
}
return newConfigs;
| public void | assertRectSetsEqual(java.lang.String message, java.util.Map before, java.util.Map after)
StringBuilder sb = new StringBuilder();
sb.append("checking rectangle equality.");
sb.append("before:\n");
for (Map.Entry<Item, Rect> entry : before.entrySet()) {
sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
}
sb.append("after:\n");
for (Map.Entry<Item, Rect> entry : after.entrySet()) {
sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
}
message = message + "\n" + sb.toString();
assertEquals(message + ":\nitem counts should be equal", before.size()
, after.size());
for (Map.Entry<Item, Rect> entry : before.entrySet()) {
Rect afterRect = after.get(entry.getKey());
assertNotNull(message + ":\nSame item should be visible after simple re-layout",
afterRect);
assertEquals(message + ":\nItem 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 + "\ntwo layout should be different", throwable);
| private TargetTuple | findInvisibleTarget(android.support.v7.widget.LinearLayoutManagerTest$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 +
(mRecyclerView.getAdapter().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 | getFirstLastChildrenTest(android.support.v7.widget.LinearLayoutManagerTest$Config config)
setupByConfig(config, true);
Runnable viewInBoundsTest = new Runnable() {
@Override
public void run() {
VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
final String boundsLog = mLayoutManager.getBoundsLog();
assertEquals(config + ":\nfirst visible child should match traversal result\n"
+ boundsLog, visibleChildren.firstVisiblePosition,
mLayoutManager.findFirstVisibleItemPosition()
);
assertEquals(
config + ":\nfirst fully visible child should match traversal result\n"
+ boundsLog, visibleChildren.firstFullyVisiblePosition,
mLayoutManager.findFirstCompletelyVisibleItemPosition()
);
assertEquals(config + ":\nlast visible child should match traversal result\n"
+ boundsLog, visibleChildren.lastVisiblePosition,
mLayoutManager.findLastVisibleItemPosition()
);
assertEquals(
config + ":\nlast fully visible child should match traversal result\n"
+ boundsLog, visibleChildren.lastFullyVisiblePosition,
mLayoutManager.findLastCompletelyVisibleItemPosition()
);
}
};
runTestOnUiThread(viewInBoundsTest);
// smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
// case
final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.smoothScrollToPosition(scrollPosition);
}
});
while (mLayoutManager.isSmoothScrolling() ||
mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
runTestOnUiThread(viewInBoundsTest);
Thread.sleep(400);
}
// delete all items
mLayoutManager.expectLayouts(2);
mTestAdapter.deleteAndNotify(0, mTestAdapter.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.mOrientationHelper.getTotalSpace();
final TestAdapter newAdapter = new TestAdapter(100) {
@Override
public void onBindViewHolder(TestViewHolder holder,
int position) {
super.onBindViewHolder(holder, position);
if (config.mOrientation == 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);
| public void | savedStateTest(android.support.v7.widget.LinearLayoutManagerTest$Config config, boolean waitForLayout, android.support.v7.widget.LinearLayoutManagerTest$PostLayoutRunnable postLayoutOperation, android.support.v7.widget.LinearLayoutManagerTest$PostRestoreRunnable postRestoreOperation)
if (DEBUG) {
Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
config + " post layout action " + postLayoutOperation.describe() +
"post restore action " + postRestoreOperation.describe());
}
setupByConfig(config, false);
if (waitForLayout) {
waitForFirstLayout();
postLayoutOperation.run();
}
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());
// this config should be no op.
mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
config.mOrientation, config.mReverseLayout);
mLayoutManager.setStackFromEnd(config.mStackFromEnd);
restored.setLayoutManager(mLayoutManager);
// use the same adapter for Rect matching
restored.setAdapter(mTestAdapter);
restored.onRestoreInstanceState(savedState);
postRestoreOperation.onAfterRestore(config);
assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
parcel.readString());
mLayoutManager.expectLayouts(1);
setRecyclerView(restored);
mLayoutManager.waitForLayout(2);
// calculate prefix here instead of above to include post restore changes
final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
"\npostRestore:" + postRestoreOperation.describe() + "\n";
assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
config.mReverseLayout, mLayoutManager.getReverseLayout());
assertEquals(logPrefix + " on saved state, orientation should be preserved",
config.mOrientation, mLayoutManager.getOrientation());
assertEquals(logPrefix + " on saved state, stack from end should be preserved",
config.mStackFromEnd, mLayoutManager.getStackFromEnd());
if (waitForLayout) {
if (postRestoreOperation.shouldLayoutMatch(config)) {
assertRectSetsEqual(
logPrefix + ": on restore, previous view positions should be preserved",
before, mLayoutManager.collectChildCoordinates());
} else {
assertRectSetsNotEqual(
logPrefix
+ ": on restore with changes, previous view positions should NOT "
+ "be preserved",
before, mLayoutManager.collectChildCoordinates());
}
postRestoreOperation.onAfterReLayout(config);
}
| void | scrollToPositionWithOffset(int position, int offset)
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mLayoutManager.scrollToPositionWithOffset(position, offset);
}
});
| public void | scrollToPositionWithOffsetTest(android.support.v7.widget.LinearLayoutManagerTest$Config config)
setupByConfig(config, true);
OrientationHelper orientationHelper = OrientationHelper
.createOrientationHelper(mLayoutManager, config.mOrientation);
Rect layoutBounds = getDecoratedRecyclerViewBounds();
// try scrolling towards head, should not affect anything
Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
if (config.mStackFromEnd) {
scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
mLayoutManager.mOrientationHelper.getEnd() - 500);
} else {
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 = config.mStackFromEnd ? startOffset + startOffset / 2
: 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 " +
" offset:" + finalOffset + " , existing offset:" + startOffset + ", "
+ "child " + position,
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);
final String logPrefix = config + " " + target;
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(logPrefix + " scrolling to a mPosition with offset " + offset
+ " should layout it", child);
final Rect bounds = mLayoutManager.getViewBounds(child);
if (DEBUG) {
Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
+ layoutBounds + " with offset " + offset);
}
if (config.mReverseLayout) {
assertEquals(logPrefix + " 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(logPrefix + " 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(VERTICAL, false, false), true);
mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
@Override
void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
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 =
mRecyclerView.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 {
mTestAdapter.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 (boolean stackFromBottom : new boolean[]{false, true}) {
mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
}
}
}
| void | setupByConfig(android.support.v7.widget.LinearLayoutManagerTest$Config config, boolean waitForFirstLayout)
mRecyclerView = new RecyclerView(getActivity());
mRecyclerView.setHasFixedSize(true);
mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
: config.mTestAdapter;
mRecyclerView.setAdapter(mTestAdapter);
mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
config.mReverseLayout);
mLayoutManager.setStackFromEnd(config.mStackFromEnd);
mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
mRecyclerView.setLayoutManager(mLayoutManager);
if (waitForFirstLayout) {
waitForFirstLayout();
}
| public void | stackFromEndTest(android.support.v7.widget.LinearLayoutManagerTest$Config config)
final FrameLayout container = getRecyclerViewContainer();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
container.setPadding(0, 0, 0, 0);
}
});
setupByConfig(config, true);
int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition();
int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
mLayoutManager.expectLayouts(1);
// resize the recycler view to half
runTestOnUiThread(new Runnable() {
@Override
public void run() {
if (config.mOrientation == HORIZONTAL) {
container.setPadding(0, 0, container.getWidth() / 2, 0);
} else {
container.setPadding(0, 0, 0, container.getWidth() / 2);
}
}
});
mLayoutManager.waitForLayout(1);
if (config.mStackFromEnd) {
assertEquals("[" + config + "]: last visible position should not change.",
lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition());
assertEquals("[" + config + "]: last completely visible position should not change",
lastCompletelyVisibleItemPosition,
mLayoutManager.findLastCompletelyVisibleItemPosition());
} else {
assertEquals("[" + config + "]: first visible position should not change.",
firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition());
assertEquals("[" + config + "]: last completely visible position should not change",
firstCompletelyVisibleItemPosition,
mLayoutManager.findFirstCompletelyVisibleItemPosition());
}
| public void | testAccessibilityPositions()
setupByConfig(new Config(VERTICAL, false, false), true);
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);
assertEquals("result should have first position",
record.getFromIndex(),
mLayoutManager.findFirstVisibleItemPosition());
assertEquals("result should have last position",
record.getToIndex(),
mLayoutManager.findLastVisibleItemPosition());
| public void | testDontRecycleChildrenOnDetach()
setupByConfig(new Config().recycleChildrenOnDetach(false), true);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
mRecyclerView.setLayoutManager(new TestLayoutManager());
assertEquals("No views are recycled", recyclerSize,
mRecyclerView.mRecycler.getRecycledViewPool().size());
}
});
| public void | testGetFirstLastChildrenTest()
for (Config config : mBaseVariations) {
getFirstLastChildrenTest(config);
}
| public void | testKeepFocusOnRelayout()
setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
int center = (mLayoutManager.findLastVisibleItemPosition()
- mLayoutManager.findFirstVisibleItemPosition()) / 2;
final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
vh.itemView.requestFocus();
}
});
assertTrue("view should have the focus", vh.itemView.hasFocus());
// add a bunch of items right before that view, make sure it keeps its position
mLayoutManager.expectLayouts(2);
final int childCountToAdd = mRecyclerView.getChildCount() * 2;
mTestAdapter.addAndNotify(center, childCountToAdd);
center += childCountToAdd; // offset item
mLayoutManager.waitForLayout(2);
mLayoutManager.waitForAnimationsToEnd(20);
final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center);
assertNotNull("focused child should stay in layout", postVH);
assertSame("same view holder should be kept for unchanged child", vh, postVH);
assertEquals("focused child's screen position should stay unchanged", top,
mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
| public void | testRecycleChildrenOnDetach()
setupByConfig(new Config().recycleChildrenOnDetach(true), true);
final int childCount = mLayoutManager.getChildCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews(
mTestAdapter.getItemViewType(0), recyclerSize + childCount);
mRecyclerView.setLayoutManager(new TestLayoutManager());
assertEquals("All children should be recycled", childCount + recyclerSize,
mRecyclerView.mRecycler.getRecycledViewPool().size());
}
});
| public void | testRecycleDuringAnimations()
final AtomicInteger childCount = new AtomicInteger(0);
final TestAdapter adapter = new TestAdapter(300) {
@Override
public TestViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
final int cnt = childCount.incrementAndGet();
final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
if (DEBUG) {
Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder);
}
return testViewHolder;
}
};
setupByConfig(new Config(VERTICAL, false, false).itemCount(300)
.adapter(adapter), true);
final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
@Override
public void putRecycledView(RecyclerView.ViewHolder scrap) {
super.putRecycledView(scrap);
int cnt = childCount.decrementAndGet();
if (DEBUG) {
Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap);
}
}
@Override
public RecyclerView.ViewHolder getRecycledView(int viewType) {
final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType);
if (recycledView != null) {
final int cnt = childCount.incrementAndGet();
if (DEBUG) {
Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView);
}
}
return recycledView;
}
};
pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500);
mRecyclerView.setRecycledViewPool(pool);
// now keep adding children to trigger more children being created etc.
for (int i = 0; i < 100; i ++) {
adapter.addAndNotify(15, 1);
Thread.sleep(15);
}
getInstrumentation().waitForIdleSync();
waitForAnimations(2);
assertEquals("Children count should add up", childCount.get(),
mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
// now trigger lots of add again, followed by a scroll to position
for (int i = 0; i < 100; i ++) {
adapter.addAndNotify(5 + (i % 3) * 3, 1);
Thread.sleep(25);
}
smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20);
waitForAnimations(2);
getInstrumentation().waitForIdleSync();
assertEquals("Children count should add up", childCount.get(),
mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
| public void | testResize()
for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5
, Config.DEFAULT_ITEM_COUNT)) {
stackFromEndTest(config);
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(mTestAdapter.getItemCount() * 3 / 4);
mLayoutManager.waitForLayout(2);
}
@Override
public String describe() {
return "scroll to position";
}
},
new PostLayoutRunnable() {
@Override
public void run() throws Throwable {
mLayoutManager.expectLayouts(1);
scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
50);
mLayoutManager.waitForLayout(2);
}
@Override
public String describe() {
return "scroll to position with positive offset";
}
},
new PostLayoutRunnable() {
@Override
public void run() throws Throwable {
mLayoutManager.expectLayouts(1);
scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
-50);
mLayoutManager.waitForLayout(2);
}
@Override
public String describe() {
return "scroll to position with negative offset";
}
}
};
PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
new PostRestoreRunnable() {
@Override
public String describe() {
return "Doing nothing";
}
},
new PostRestoreRunnable() {
@Override
void onAfterRestore(Config config) throws Throwable {
// update config as well so that restore assertions will work
config.mOrientation = 1 - config.mOrientation;
mLayoutManager.setOrientation(config.mOrientation);
}
@Override
boolean shouldLayoutMatch(Config config) {
return config.mItemCount == 0;
}
@Override
public String describe() {
return "Changing orientation";
}
},
new PostRestoreRunnable() {
@Override
void onAfterRestore(Config config) throws Throwable {
config.mStackFromEnd = !config.mStackFromEnd;
mLayoutManager.setStackFromEnd(config.mStackFromEnd);
}
@Override
boolean shouldLayoutMatch(Config config) {
return true; //stack from end should not move items on change
}
@Override
public String describe() {
return "Changing stack from end";
}
},
new PostRestoreRunnable() {
@Override
void onAfterRestore(Config config) throws Throwable {
config.mReverseLayout = !config.mReverseLayout;
mLayoutManager.setReverseLayout(config.mReverseLayout);
}
@Override
boolean shouldLayoutMatch(Config config) {
return config.mItemCount == 0;
}
@Override
public String describe() {
return "Changing reverse layout";
}
},
new PostRestoreRunnable() {
@Override
void onAfterRestore(Config config) throws Throwable {
config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
}
@Override
boolean shouldLayoutMatch(Config config) {
return true;
}
@Override
String describe() {
return "Change should recycle children";
}
},
new PostRestoreRunnable() {
int position;
@Override
void onAfterRestore(Config config) throws Throwable {
position = mTestAdapter.getItemCount() / 2;
mLayoutManager.scrollToPosition(position);
}
@Override
boolean shouldLayoutMatch(Config config) {
return mTestAdapter.getItemCount() == 0;
}
@Override
String describe() {
return "Scroll to position " + position ;
}
@Override
void onAfterReLayout(Config config) {
if (mTestAdapter.getItemCount() > 0) {
assertEquals(config + ":scrolled view should be last completely visible",
position,
config.mStackFromEnd ?
mLayoutManager.findLastCompletelyVisibleItemPosition()
: mLayoutManager.findFirstCompletelyVisibleItemPosition());
}
}
}
};
boolean[] waitForLayoutOptions = new boolean[]{true, false};
List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300);
variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
for (Config config : variations) {
for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
for (boolean waitForLayout : waitForLayoutOptions) {
for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable,
postRestoreRunnable);
removeRecyclerView();
}
}
}
}
| public void | testScrollToPositionWithOffset()
for (Config config : mBaseVariations) {
scrollToPositionWithOffsetTest(config.itemCount(300));
removeRecyclerView();
}
| public void | testScrollToPositionWithPredictive()
scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
scrollToPositionWithPredictive(3, 20);
removeRecyclerView();
scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
| private void | waitForFirstLayout()
mLayoutManager.expectLayouts(1);
setRecyclerView(mRecyclerView);
mLayoutManager.waitForLayout(2);
|
|