Add tests for edge view to the LinearLayoutManagerSnappingTest
Also, stabilize the Linear- and GridLayoutManagerSnappingTest
as they seemed to have a flaky setup.
Bug:31399980
Change-Id:If7780a5cc863a53cf4df946d186dfd5ae28274cc
diff --git a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
index 915556e..37197e4 100644
--- a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
@@ -53,6 +53,7 @@
}
}
+ @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dx != 0 || dy != 0) {
mScrolled = true;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index 3a72152..9109c87 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -16,6 +16,7 @@
package android.support.v7.widget;
import android.content.Context;
+import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
@@ -152,6 +153,8 @@
CountDownLatch prefetchLatch;
+ OrientationHelper mSecondaryOrientation;
+
List<GridLayoutManagerTest.Callback>
mCallbacks = new ArrayList<GridLayoutManagerTest.Callback>();
@@ -182,6 +185,26 @@
}
@Override
+ public void setOrientation(int orientation) {
+ super.setOrientation(orientation);
+ mSecondaryOrientation = null;
+ }
+
+ @Override
+ void ensureLayoutState() {
+ super.ensureLayoutState();
+ if (mSecondaryOrientation == null) {
+ if (getOrientation() == RecyclerView.HORIZONTAL) {
+ mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
+ RecyclerView.VERTICAL);
+ } else {
+ mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
+ RecyclerView.HORIZONTAL);
+ }
+ }
+ }
+
+ @Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
for (GridLayoutManagerTest.Callback callback : mCallbacks) {
@@ -213,6 +236,23 @@
};
}
+ Rect getViewBounds(View view) {
+ if (getOrientation() == HORIZONTAL) {
+ return new Rect(
+ mOrientationHelper.getDecoratedStart(view),
+ mSecondaryOrientation.getDecoratedStart(view),
+ mOrientationHelper.getDecoratedEnd(view),
+ mSecondaryOrientation.getDecoratedEnd(view));
+ } else {
+ return new Rect(
+ mSecondaryOrientation.getDecoratedStart(view),
+ mOrientationHelper.getDecoratedStart(view),
+ mSecondaryOrientation.getDecoratedEnd(view),
+ mOrientationHelper.getDecoratedEnd(view));
+ }
+
+ }
+
public void expectLayout(int layoutCount) {
mLayoutLatch = new CountDownLatch(layoutCount);
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
index 50c9ba3..54190f1 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -21,12 +21,14 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.content.Context;
import android.graphics.Rect;
+import android.support.annotation.Nullable;
import android.support.v4.util.Pair;
import android.util.Log;
import android.view.View;
@@ -83,14 +85,21 @@
}
void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
+ setupByConfig(config, waitForFirstLayout, null, null);
+ }
+
+ void setupByConfig(Config config, boolean waitForFirstLayout,
+ @Nullable RecyclerView.LayoutParams childLayoutParams,
+ @Nullable RecyclerView.LayoutParams parentLayoutParams) throws Throwable {
mRecyclerView = inflateWrappedRV();
mRecyclerView.setHasFixedSize(true);
- mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
+ mTestAdapter = config.mTestAdapter == null
+ ? new TestAdapter(config.mItemCount, childLayoutParams)
: config.mTestAdapter;
mRecyclerView.setAdapter(mTestAdapter);
mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
- config.mReverseLayout);
+ config.mReverseLayout);
mLayoutManager.setStackFromEnd(config.mStackFromEnd);
mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
mRecyclerView.setLayoutManager(mLayoutManager);
@@ -102,6 +111,10 @@
)
);
}
+ if (parentLayoutParams != null) {
+ mRecyclerView.setLayoutParams(parentLayoutParams);
+ }
+
if (waitForFirstLayout) {
waitForFirstLayout();
}
@@ -342,15 +355,26 @@
class WrappedLinearLayoutManager extends LinearLayoutManager {
CountDownLatch layoutLatch;
-
CountDownLatch snapLatch;
-
CountDownLatch prefetchLatch;
+ CountDownLatch callbackLatch;
OrientationHelper mSecondaryOrientation;
OnLayoutListener mOnLayoutListener;
+ RecyclerView.OnScrollListener mCallbackListener = new RecyclerView.OnScrollListener() {
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ callbackLatch.countDown();
+ if (callbackLatch.getCount() == 0L) {
+ removeOnScrollListener(this);
+ }
+ }
+ };
+
public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
@@ -359,6 +383,15 @@
layoutLatch = new CountDownLatch(count);
}
+ public void expectCallbacks(int count) throws Throwable {
+ callbackLatch = new CountDownLatch(count);
+ mRecyclerView.addOnScrollListener(mCallbackListener);
+ }
+
+ private void removeOnScrollListener(RecyclerView.OnScrollListener listener) {
+ mRecyclerView.removeOnScrollListener(listener);
+ }
+
public void waitForLayout(int seconds) throws Throwable {
layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
checkForMainThreadException();
@@ -372,6 +405,13 @@
});
}
+ public void assertNoCallbacks(String msg, long timeout) throws Throwable {
+ callbackLatch.await(timeout, TimeUnit.SECONDS);
+ long latchCount = callbackLatch.getCount();
+ assertFalse(msg + " :" + latchCount, latchCount == 0);
+ removeOnScrollListener(mCallbackListener);
+ }
+
public void expectPrefetch(int count) {
prefetchLatch = new CountDownLatch(count);
}
@@ -413,8 +453,7 @@
// use a runnable to ensure RV layout is finished
getInstrumentation().runOnMainSync(new Runnable() {
@Override
- public void run() {
- }
+ public void run() {}
});
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 7a7a4d4..0373251 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -27,6 +27,7 @@
import android.app.Instrumentation;
import android.graphics.Rect;
import android.os.Looper;
+import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.v4.view.ViewCompat;
@@ -791,10 +792,16 @@
ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
List<Item> mItems;
+ final @Nullable RecyclerView.LayoutParams mLayoutParams;
public TestAdapter(int count) {
+ this(count, null);
+ }
+
+ public TestAdapter(int count, @Nullable RecyclerView.LayoutParams layoutParams) {
mItems = new ArrayList<Item>(count);
addItems(0, count, DEFAULT_ITEM_PREFIX);
+ mLayoutParams = layoutParams;
}
private void addItems(int pos, int count, String prefix) {
@@ -848,6 +855,9 @@
final Item item = mItems.get(position);
((TextView) (holder.itemView)).setText(item.mText + "(" + item.mId + ")");
holder.mBoundItem = item;
+ if (mLayoutParams != null) {
+ holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(mLayoutParams));
+ }
}
public Item getItemAt(int position) {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerSnappingTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerSnappingTest.java
index 0ba8fed..6dc80bd 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerSnappingTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerSnappingTest.java
@@ -168,7 +168,11 @@
mGlm.waitForLayout(2);
View view = findCenterView(mGlm);
- int scrollDistance = (getViewDimension(view) / 2) + 10;
+ int scrollDistance = distFromCenter(view) / 2;
+ if (scrollDistance == 0) {
+ return;
+ }
+
int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
mGlm.expectIdleState(2);
@@ -208,6 +212,14 @@
}
}
+ private int distFromCenter(View view) {
+ if (mGlm.canScrollHorizontally()) {
+ return Math.abs(mRecyclerView.getWidth() / 2 - mGlm.getViewBounds(view).centerX());
+ } else {
+ return Math.abs(mRecyclerView.getHeight() / 2 - mGlm.getViewBounds(view).centerY());
+ }
+ }
+
private boolean fling(final int velocityX, final int velocityY)
throws Throwable {
final AtomicBoolean didStart = new AtomicBoolean(false);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSnappingTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSnappingTest.java
index ae19fc1..3d978da 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSnappingTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSnappingTest.java
@@ -59,6 +59,32 @@
@MediumTest
@Test
+ public void snapOnScrollSameViewEdge() throws Throwable {
+ final Config config = (Config) mConfig.clone();
+ // Ensure that the views are big enough to reach the pathological case when the view closest
+ // to the center is an edge view, but it cannot scroll further in order to snap.
+ setupByConfig(config, true, new RecyclerView.LayoutParams(1000, 1000),
+ new RecyclerView.LayoutParams(1500, 1500));
+ SnapHelper snapHelper = new LinearSnapHelper();
+ mLayoutManager.expectIdleState(1);
+ snapHelper.attachToRecyclerView(mRecyclerView);
+ mLayoutManager.waitForSnap(10);
+
+ // Record the current center view.
+ View view = findCenterView(mLayoutManager);
+
+ int scrollDistance = (getViewDimension(view) / 2) - 1;
+ int scrollDist = config.mStackFromEnd == config.mReverseLayout
+ ? -scrollDistance : scrollDistance;
+ mLayoutManager.expectIdleState(1);
+ smoothScrollBy(scrollDist);
+ mLayoutManager.waitForSnap(10);
+ mLayoutManager.expectCallbacks(5);
+ mLayoutManager.assertNoCallbacks("There should be no callbacks after some time", 3);
+ }
+
+ @MediumTest
+ @Test
public void snapOnScrollSameView() throws Throwable {
final Config config = (Config) mConfig.clone();
setupByConfig(config, true);
@@ -76,7 +102,7 @@
// Views have not changed
View viewAfterFling = findCenterView(mLayoutManager);
- assertSame("The view should have scrolled", view, viewAfterFling);
+ assertSame("The view should NOT have scrolled", view, viewAfterFling);
assertCenterAligned(viewAfterFling);
}
@@ -165,7 +191,11 @@
mLayoutManager.waitForLayout(2);
View view = findCenterView(mLayoutManager);
- int scrollDistance = (getViewDimension(view) / 2) + 10;
+ int scrollDistance = distFromCenter(view) / 2;
+ if (scrollDistance == 0) {
+ return;
+ }
+
int scrollDist = mReverseScroll ? -scrollDistance : scrollDistance;
mLayoutManager.expectIdleState(2);
@@ -201,6 +231,16 @@
}
}
+ private int distFromCenter(View view) {
+ if (mLayoutManager.canScrollHorizontally()) {
+ return Math.abs(mRecyclerView.getWidth() / 2 -
+ mLayoutManager.getViewBounds(view).centerX());
+ } else {
+ return Math.abs(mRecyclerView.getHeight() / 2 -
+ mLayoutManager.getViewBounds(view).centerY());
+ }
+ }
+
private boolean fling(final int velocityX, final int velocityY) throws Throwable {
final AtomicBoolean didStart = new AtomicBoolean(false);
runTestOnUiThread(new Runnable() {