blob: 0fea8d650b4d03865e596d84aa1a51aa652532b5 [file] [log] [blame]
package android.support.v7.widget;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static android.support.v7.widget.LayoutState.LAYOUT_END;
import static android.support.v7.widget.LayoutState.LAYOUT_START;
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
import static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
import static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static java.util.concurrent.TimeUnit.SECONDS;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
public class BaseStaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
protected static final boolean DEBUG = false;
protected static final int AVG_ITEM_PER_VIEW = 3;
protected static final String TAG = "StaggeredGridLayoutManagerTest";
volatile WrappedLayoutManager mLayoutManager;
GridTestAdapter mAdapter;
protected static List<Config> createBaseVariations() {
List<Config> variations = new ArrayList<>();
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}) {
for (boolean wrap : new boolean[]{true, false}) {
variations.add(new Config(orientation, reverseLayout, spanCount,
gapStrategy).wrap(wrap));
}
}
}
}
}
return variations;
}
protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
Object... variations)
throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
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;
}
void setupByConfig(Config config) throws Throwable {
setupByConfig(config, new GridTestAdapter(config.mItemCount, config.mOrientation));
}
void setupByConfig(Config config, GridTestAdapter adapter) throws Throwable {
mAdapter = adapter;
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 {
StaggeredGridLayoutManager.LayoutParams
lp = (StaggeredGridLayoutManager.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);
}
}
});
}
StaggeredGridLayoutManager.LayoutParams getLp(View view) {
return (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
}
void waitFirstLayout() throws Throwable {
mLayoutManager.expectLayouts(1);
setRecyclerView(mRecyclerView);
mLayoutManager.waitForLayout(3);
getInstrumentation().waitForIdleSync();
}
/**
* enqueues an empty runnable to main thread so that we can be assured it did run
*
* @param count Number of times to run
*/
protected void waitForMainThread(int count) throws Throwable {
final AtomicInteger i = new AtomicInteger(count);
while (i.get() > 0) {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
i.decrementAndGet();
}
});
}
}
public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
Map<Item, Rect> after) {
Throwable throwable = null;
try {
assertRectSetsEqual("NOT " + message, before, after);
} catch (Throwable t) {
throwable = t;
}
assertNotNull(message + " two layout should be different", throwable);
}
public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
assertRectSetsEqual(message, before, after, true);
}
public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
boolean strictItemEquality) {
StringBuilder log = new StringBuilder();
if (DEBUG) {
log.append("checking rectangle equality.\n");
log.append("total space:" + mLayoutManager.mPrimaryOrientation.getTotalSpace());
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()) {
final Item beforeItem = entry.getKey();
Rect afterRect = null;
if (strictItemEquality) {
afterRect = after.get(beforeItem);
assertNotNull(message + ": Same item should be visible after simple re-layout",
afterRect);
} else {
for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
final Item afterItem = afterEntry.getKey();
if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
afterRect = afterEntry.getValue();
break;
}
}
assertNotNull(message + ": Item with same adapter index should be visible " +
"after simple re-layout",
afterRect);
}
assertEquals(message + ": Item should be laid out at the same coordinates",
entry.getValue(),
afterRect);
}
}
protected void assertViewPositions(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)
);
}
}
}
protected TargetTuple findInvisibleTarget(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);
}
protected void scrollToPositionWithOffset(final int position, final int offset)
throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mLayoutManager.scrollToPositionWithOffset(position, offset);
}
});
}
static class OnLayoutListener {
void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
}
void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
}
}
static class VisibleChildren {
int[] firstVisiblePositions;
int[] firstFullyVisiblePositions;
int[] lastVisiblePositions;
int[] lastFullyVisiblePositions;
View findFirstPartialVisibleClosestToStart;
View findFirstPartialVisibleClosestToEnd;
VisibleChildren(int spanCount) {
firstFullyVisiblePositions = new int[spanCount];
firstVisiblePositions = new int[spanCount];
lastVisiblePositions = new int[spanCount];
lastFullyVisiblePositions = new int[spanCount];
for (int i = 0; i < spanCount; i++) {
firstFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
firstVisiblePositions[i] = RecyclerView.NO_POSITION;
lastVisiblePositions[i] = RecyclerView.NO_POSITION;
lastFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
VisibleChildren that = (VisibleChildren) o;
if (!Arrays.equals(firstFullyVisiblePositions, that.firstFullyVisiblePositions)) {
return false;
}
if (findFirstPartialVisibleClosestToStart
!= null ? !findFirstPartialVisibleClosestToStart
.equals(that.findFirstPartialVisibleClosestToStart)
: that.findFirstPartialVisibleClosestToStart != null) {
return false;
}
if (!Arrays.equals(firstVisiblePositions, that.firstVisiblePositions)) {
return false;
}
if (!Arrays.equals(lastFullyVisiblePositions, that.lastFullyVisiblePositions)) {
return false;
}
if (findFirstPartialVisibleClosestToEnd != null ? !findFirstPartialVisibleClosestToEnd
.equals(that.findFirstPartialVisibleClosestToEnd)
: that.findFirstPartialVisibleClosestToEnd
!= null) {
return false;
}
if (!Arrays.equals(lastVisiblePositions, that.lastVisiblePositions)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = Arrays.hashCode(firstVisiblePositions);
result = 31 * result + Arrays.hashCode(firstFullyVisiblePositions);
result = 31 * result + Arrays.hashCode(lastVisiblePositions);
result = 31 * result + Arrays.hashCode(lastFullyVisiblePositions);
result = 31 * result + (findFirstPartialVisibleClosestToStart != null
? findFirstPartialVisibleClosestToStart
.hashCode() : 0);
result = 31 * result + (findFirstPartialVisibleClosestToEnd != null
? findFirstPartialVisibleClosestToEnd
.hashCode()
: 0);
return result;
}
@Override
public String toString() {
return "VisibleChildren{" +
"firstVisiblePositions=" + Arrays.toString(firstVisiblePositions) +
", firstFullyVisiblePositions=" + Arrays.toString(firstFullyVisiblePositions) +
", lastVisiblePositions=" + Arrays.toString(lastVisiblePositions) +
", lastFullyVisiblePositions=" + Arrays.toString(lastFullyVisiblePositions) +
", findFirstPartialVisibleClosestToStart=" +
viewToString(findFirstPartialVisibleClosestToStart) +
", findFirstPartialVisibleClosestToEnd=" +
viewToString(findFirstPartialVisibleClosestToEnd) +
'}';
}
private String viewToString(View view) {
if (view == null) {
return null;
}
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof RecyclerView.LayoutParams == false) {
return System.identityHashCode(view) + "(?)";
}
RecyclerView.LayoutParams rvlp = (RecyclerView.LayoutParams) lp;
return System.identityHashCode(view) + "(" + rvlp.getViewAdapterPosition() + ")";
}
}
abstract static class OnBindCallback {
abstract void onBoundItem(TestViewHolder vh, int position);
boolean assignRandomSize() {
return true;
}
void onCreatedViewHolder(TestViewHolder vh) {
}
}
static class Config implements Cloneable {
static final int DEFAULT_ITEM_COUNT = 300;
int mOrientation = OrientationHelper.VERTICAL;
boolean mReverseLayout = false;
int mSpanCount = 3;
int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
int mItemCount = DEFAULT_ITEM_COUNT;
boolean mWrap = false;
Config(int orientation, boolean reverseLayout, int spanCount, int gapStrategy) {
mOrientation = orientation;
mReverseLayout = reverseLayout;
mSpanCount = spanCount;
mGapStrategy = gapStrategy;
}
public Config() {
}
Config orientation(int orientation) {
mOrientation = orientation;
return this;
}
Config reverseLayout(boolean reverseLayout) {
mReverseLayout = reverseLayout;
return this;
}
Config spanCount(int spanCount) {
mSpanCount = spanCount;
return this;
}
Config gapStrategy(int gapStrategy) {
mGapStrategy = gapStrategy;
return this;
}
public Config itemCount(int itemCount) {
mItemCount = itemCount;
return this;
}
public Config wrap(boolean wrap) {
mWrap = wrap;
return this;
}
@Override
public String toString() {
return "[CONFIG:" +
" span:" + mSpanCount + "," +
" orientation:" + (mOrientation == HORIZONTAL ? "horz," : "vert,") +
" reverse:" + (mReverseLayout ? "T" : "F") +
" itemCount:" + mItemCount +
" wrapContent:" + mWrap +
" gap strategy: " + gapStrategyName(mGapStrategy);
}
protected static String gapStrategyName(int gapStrategy) {
switch (gapStrategy) {
case GAP_HANDLING_NONE:
return "none";
case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
return "move spans";
}
return "gap strategy: unknown";
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class WrappedLayoutManager extends StaggeredGridLayoutManager {
CountDownLatch layoutLatch;
OnLayoutListener mOnLayoutListener;
// gradle does not yet let us customize manifest for tests which is necessary to test RTL.
// until bug is fixed, we'll fake it.
// public issue id: 57819
Boolean mFakeRTL;
@Override
boolean isLayoutRTL() {
return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
}
public void expectLayouts(int count) {
layoutLatch = new CountDownLatch(count);
}
public void waitForLayout(int seconds) throws Throwable {
layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
checkForMainThreadException();
MatcherAssert.assertThat("all layouts should complete on time",
layoutLatch.getCount(), CoreMatchers.is(0L));
// use a runnable to ensure RV layout is finished
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
}
});
}
public void assertNoLayout(String msg, long timeout) throws Throwable {
layoutLatch.await(timeout, TimeUnit.SECONDS);
assertFalse(msg, layoutLatch.getCount() == 0);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
String before;
if (DEBUG) {
before = layoutToString("before");
} else {
before = "enable DEBUG";
}
try {
if (mOnLayoutListener != null) {
mOnLayoutListener.before(recycler, state);
}
super.onLayoutChildren(recycler, state);
if (mOnLayoutListener != null) {
mOnLayoutListener.after(recycler, state);
}
validateChildren(before);
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
layoutLatch.countDown();
}
@Override
int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
int result = super.scrollBy(dt, recycler, state);
validateChildren();
return result;
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
return 0;
}
public WrappedLayoutManager(int spanCount, int orientation) {
super(spanCount, orientation);
}
ArrayList<ArrayList<View>> collectChildrenBySpan() {
ArrayList<ArrayList<View>> viewsBySpan = new ArrayList<ArrayList<View>>();
for (int i = 0; i < getSpanCount(); i++) {
viewsBySpan.add(new ArrayList<View>());
}
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
LayoutParams lp
= (LayoutParams) view
.getLayoutParams();
viewsBySpan.get(lp.mSpan.mIndex).add(view);
}
return viewsBySpan;
}
@Nullable
@Override
public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
RecyclerView.State state) {
View result = null;
try {
result = super.onFocusSearchFailed(focused, direction, recycler, state);
validateChildren();
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
return result;
}
Rect getViewBounds(View view) {
if (getOrientation() == HORIZONTAL) {
return new Rect(
mPrimaryOrientation.getDecoratedStart(view),
mSecondaryOrientation.getDecoratedStart(view),
mPrimaryOrientation.getDecoratedEnd(view),
mSecondaryOrientation.getDecoratedEnd(view));
} else {
return new Rect(
mSecondaryOrientation.getDecoratedStart(view),
mPrimaryOrientation.getDecoratedStart(view),
mSecondaryOrientation.getDecoratedEnd(view),
mPrimaryOrientation.getDecoratedEnd(view));
}
}
public String getBoundsLog() {
StringBuilder sb = new StringBuilder();
sb.append("view bounds:[start:").append(mPrimaryOrientation.getStartAfterPadding())
.append(",").append(" end").append(mPrimaryOrientation.getEndAfterPadding());
sb.append("\nchildren bounds\n");
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
.append("[").append("start:").append(
mPrimaryOrientation.getDecoratedStart(child)).append(", end:")
.append(mPrimaryOrientation.getDecoratedEnd(child)).append("]\n");
}
return sb.toString();
}
public VisibleChildren traverseAndFindVisibleChildren() {
int childCount = getChildCount();
final VisibleChildren visibleChildren = new VisibleChildren(getSpanCount());
final int start = mPrimaryOrientation.getStartAfterPadding();
final int end = mPrimaryOrientation.getEndAfterPadding();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
final int childStart = mPrimaryOrientation.getDecoratedStart(child);
final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
final boolean fullyVisible = childStart >= start && childEnd <= end;
final boolean hidden = childEnd <= start || childStart >= end;
if (hidden) {
continue;
}
final int position = getPosition(child);
final int span = getLp(child).getSpanIndex();
if (fullyVisible) {
if (position < visibleChildren.firstFullyVisiblePositions[span] ||
visibleChildren.firstFullyVisiblePositions[span]
== RecyclerView.NO_POSITION) {
visibleChildren.firstFullyVisiblePositions[span] = position;
}
if (position > visibleChildren.lastFullyVisiblePositions[span]) {
visibleChildren.lastFullyVisiblePositions[span] = position;
}
}
if (position < visibleChildren.firstVisiblePositions[span] ||
visibleChildren.firstVisiblePositions[span] == RecyclerView.NO_POSITION) {
visibleChildren.firstVisiblePositions[span] = position;
}
if (position > visibleChildren.lastVisiblePositions[span]) {
visibleChildren.lastVisiblePositions[span] = position;
}
if (visibleChildren.findFirstPartialVisibleClosestToStart == null) {
visibleChildren.findFirstPartialVisibleClosestToStart = child;
}
visibleChildren.findFirstPartialVisibleClosestToEnd = child;
}
return visibleChildren;
}
Map<Item, Rect> collectChildCoordinates() throws Throwable {
final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// do it if and only if child is visible
if (child.getRight() < 0 || child.getBottom() < 0 ||
child.getLeft() >= getWidth() || child.getTop() >= getHeight()) {
// invisible children may be drawn in cases like scrolling so we should
// ignore them
continue;
}
LayoutParams lp = (LayoutParams) child
.getLayoutParams();
TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
items.put(vh.mBoundItem, getViewBounds(child));
}
}
});
return items;
}
public void setFakeRtl(Boolean fakeRtl) {
mFakeRTL = fakeRtl;
try {
requestLayoutOnUIThread(mRecyclerView);
} catch (Throwable throwable) {
postExceptionToInstrumentation(throwable);
}
}
String layoutToString(String hint) {
StringBuilder sb = new StringBuilder();
sb.append("LAYOUT POSITIONS AND INDICES ").append(hint).append("\n");
for (int i = 0; i < getChildCount(); i++) {
final View view = getChildAt(i);
final LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
sb.append(String.format("index: %d pos: %d top: %d bottom: %d span: %d isFull:%s",
i, getPosition(view),
mPrimaryOrientation.getDecoratedStart(view),
mPrimaryOrientation.getDecoratedEnd(view),
layoutParams.getSpanIndex(), layoutParams.isFullSpan())).append("\n");
}
return sb.toString();
}
protected void validateChildren() {
validateChildren(null);
}
private void validateChildren(String msg) {
if (getChildCount() == 0 || mRecyclerView.mState.isPreLayout()) {
return;
}
final int dir = mShouldReverseLayout ? -1 : 1;
int i = 0;
int pos = -1;
while (i < getChildCount()) {
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
if (lp.isItemRemoved()) {
i++;
continue;
}
pos = getPosition(getChildAt(i));
break;
}
if (pos == -1) {
return;
}
while (++i < getChildCount()) {
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
if (lp.isItemRemoved()) {
continue;
}
pos += dir;
if (getPosition(getChildAt(i)) != pos) {
throw new RuntimeException("INVALID POSITION FOR CHILD " + i + "\n" +
layoutToString("ERROR") + "\n msg:" + msg);
}
}
}
}
class GridTestAdapter extends TestAdapter {
int mOrientation;
int mRecyclerViewWidth;
int mRecyclerViewHeight;
Integer mSizeReference = null;
// original ids of items that should be full span
HashSet<Integer> mFullSpanItems = new HashSet<Integer>();
protected boolean mViewsHaveEqualSize = false; // size in the scrollable direction
protected OnBindCallback mOnBindCallback;
GridTestAdapter(int count, int orientation) {
super(count);
mOrientation = orientation;
}
@Override
public TestViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
mRecyclerViewWidth = parent.getWidth();
mRecyclerViewHeight = parent.getHeight();
TestViewHolder vh = super.onCreateViewHolder(parent, viewType);
if (mOnBindCallback != null) {
mOnBindCallback.onCreatedViewHolder(vh);
}
return vh;
}
@Override
public void offsetOriginalIndices(int start, int offset) {
if (mFullSpanItems.size() > 0) {
HashSet<Integer> old = mFullSpanItems;
mFullSpanItems = new HashSet<Integer>();
for (Integer i : old) {
if (i < start) {
mFullSpanItems.add(i);
} else if (offset > 0 || (start + Math.abs(offset)) <= i) {
mFullSpanItems.add(i + offset);
} else if (DEBUG) {
Log.d(TAG, "removed full span item " + i);
}
}
}
super.offsetOriginalIndices(start, offset);
}
@Override
protected void moveInUIThread(int from, int to) {
boolean setAsFullSpanAgain = mFullSpanItems.contains(from);
super.moveInUIThread(from, to);
if (setAsFullSpanAgain) {
mFullSpanItems.add(to);
}
}
@Override
public void onBindViewHolder(TestViewHolder holder,
int position) {
if (mSizeReference == null) {
mSizeReference = mOrientation == OrientationHelper.HORIZONTAL ? mRecyclerViewWidth
/ AVG_ITEM_PER_VIEW : mRecyclerViewHeight / AVG_ITEM_PER_VIEW;
}
super.onBindViewHolder(holder, position);
Item item = mItems.get(position);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
.getLayoutParams();
if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
((StaggeredGridLayoutManager.LayoutParams) lp)
.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
} else {
StaggeredGridLayoutManager.LayoutParams slp
= (StaggeredGridLayoutManager.LayoutParams) mLayoutManager
.generateDefaultLayoutParams();
holder.itemView.setLayoutParams(slp);
slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
lp = slp;
}
if (mOnBindCallback == null || mOnBindCallback.assignRandomSize()) {
final int minSize = mViewsHaveEqualSize ? mSizeReference :
mSizeReference + 20 * (item.mId % 10);
if (mOrientation == OrientationHelper.HORIZONTAL) {
holder.itemView.setMinimumWidth(minSize);
} else {
holder.itemView.setMinimumHeight(minSize);
}
lp.topMargin = 3;
lp.leftMargin = 5;
lp.rightMargin = 7;
lp.bottomMargin = 9;
}
if (mOnBindCallback != null) {
mOnBindCallback.onBoundItem(holder, position);
}
}
}
}