提交 24edb521 编写于 作者: T Takeshi Hagikura 提交者: GitHub

Fix the issue if the anchor view is picked from the middle of the flex line. (#180)

The anchor view should be the view, which has the
- minimum start position for the start anchor
- maximum end position for the end anchor

but if the view in the middle of the first visible flex line (or last visible
flex line for the end anchor) had the minimum start position, the anchor
position wasn't picked correctly.

This PR fixes the issue.
上级 84da1c08
......@@ -1966,6 +1966,115 @@ public class FlexboxLayoutManagerTest {
isEqualAllowingError(TestUtil.dpToPixel(activity, 120)));
}
@Test
@FlakyTest
public void testFirstReferenceView_middleOf_line_used_as_anchor() throws Throwable {
final FlexboxTestActivity activity = mActivityRule.getActivity();
final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
final TestAdapter adapter = new TestAdapter();
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.setContentView(R.layout.recyclerview);
RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview);
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setAlignItems(AlignItems.FLEX_END);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
FlexboxLayoutManager.LayoutParams lp1 = createLayoutParams(activity, 100, 80);
adapter.addItem(lp1);
// The second view in the first line has the maximum height in the same line
FlexboxLayoutManager.LayoutParams lp2 = createLayoutParams(activity, 100, 180);
adapter.addItem(lp2);
FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 100, 80);
adapter.addItem(lp3);
for (int i = 0; i < 30; i++) {
FlexboxLayoutManager.LayoutParams lp = createLayoutParams(activity, 100, 80);
adapter.addItem(lp);
}
// RecyclerView width: 320, height: 240.
}
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
// By this moment reached to the bottom
// Now scrolling to the top to see if the views in the first flex line is correctly placed
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
GeneralLocation.BOTTOM_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
GeneralLocation.BOTTOM_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
GeneralLocation.BOTTOM_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
GeneralLocation.BOTTOM_CENTER));
assertThat(layoutManager.getFlexDirection(), is(FlexDirection.ROW));
// The top coordinate of the first view should be the height of the second view minus the
// height of the first view (180 - 80)
assertThat(layoutManager.getChildAt(0).getTop(),
isEqualAllowingError(TestUtil.dpToPixel(activity, 100)));
}
@Test
@FlakyTest
public void testLastReferenceView_middleOf_line_used_as_anchor() throws Throwable {
final FlexboxTestActivity activity = mActivityRule.getActivity();
final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
final TestAdapter adapter = new TestAdapter();
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.setContentView(R.layout.recyclerview);
RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview);
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setAlignItems(AlignItems.FLEX_START);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
for (int i = 0; i < 30; i++) {
FlexboxLayoutManager.LayoutParams lp = createLayoutParams(activity, 100, 80);
adapter.addItem(lp);
}
FlexboxLayoutManager.LayoutParams lp1 = createLayoutParams(activity, 100, 80);
adapter.addItem(lp1);
// The second view in the last line has the maximum height in the same line
FlexboxLayoutManager.LayoutParams lp2 = createLayoutParams(activity, 100, 180);
adapter.addItem(lp2);
FlexboxLayoutManager.LayoutParams lp3 = createLayoutParams(activity, 100, 80);
adapter.addItem(lp3);
// RecyclerView width: 320, height: 240.
}
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
GeneralLocation.TOP_CENTER));
// By this moment reached to the bottom
assertThat(layoutManager.getFlexDirection(), is(FlexDirection.ROW));
// The bottom coordinate of the first view in the last line should be the height of the
// second view in the last line minus the height of the first view in the last line
// (180 - 80)
assertThat(layoutManager.getChildAt(layoutManager.getChildCount() - 2).getBottom() -
layoutManager.getChildAt(layoutManager.getChildCount() - 3).getBottom(),
isEqualAllowingError(TestUtil.dpToPixel(activity, 100)));
}
/**
* Creates a new flex item.
*
......
......@@ -333,7 +333,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
*/
@Override
public View getFlexItemAt(int index) {
// Look up from the scrap first to avoid the same view holder is created from the adpater
// Look up from the scrap first to avoid the same view holder is created from the adapter
// again
List<RecyclerView.ViewHolder> scrapList = mRecycler.getScrapList();
for (int i = 0, scrapCount = scrapList.size(); i < scrapCount; i++) {
......@@ -455,6 +455,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
if (childCount == 0 && state.isPreLayout()) {
return;
}
resolveLayoutDirection();
ensureOrientationHelper();
ensureLayoutState();
mFlexboxHelper.ensureMeasureSpecCache(childCount);
......@@ -471,7 +472,6 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
// view holders to be inflated at least once, which is inefficient if the number of items
// in the adapter is large
resolveLayoutDirection();
updateLayoutStateToFillEnd(mAnchorInfo);
detachAndScrapAttachedViews(recycler);
if (DEBUG) {
......@@ -633,12 +633,44 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
return false;
}
/**
* Find the reference view to be used as an anchor. It tries to find the view who has the
* maximum/minimum start/end (differs depending on if the container if RTL and the main axis
* direction) coordinate in the first visible flex line.
*
* @param state the current RecyclerView state
* @return the reference view
*/
private View findFirstReferenceChild(RecyclerView.State state) {
return findReferenceChild(0, getChildCount(), state.getItemCount());
assert mFlexboxHelper.mIndexToFlexLine != null;
View firstFound = findReferenceChild(0, getChildCount(), state.getItemCount());
if (firstFound == null) {
return null;
}
int firstFoundPosition = getPosition(firstFound);
int firstFoundLinePosition = mFlexboxHelper.mIndexToFlexLine[firstFoundPosition];
FlexLine firstFoundLine = mFlexLines.get(firstFoundLinePosition);
return findFirstReferenceViewInLine(firstFound, firstFoundLine);
}
/**
* Find the reference view to be used as an anchor. It tries to find the view who has the
* maximum/minimum start/end (differs depending on if the container if RTL and the main axis
* direction) coordinate in the last visible flex line.
*
* @param state the current RecyclerView state
* @return the reference view
*/
private View findLastReferenceChild(RecyclerView.State state) {
return findReferenceChild(getChildCount() - 1, -1, state.getItemCount());
assert mFlexboxHelper.mIndexToFlexLine != null;
View lastFound = findReferenceChild(getChildCount() - 1, -1, state.getItemCount());
if (lastFound == null) {
return null;
}
int lastFoundPosition = getPosition(lastFound);
int lastFoundLinePosition = mFlexboxHelper.mIndexToFlexLine[lastFoundPosition];
FlexLine lastFoundLine = mFlexLines.get(lastFoundLinePosition);
return findLastReferenceViewInLine(lastFound, lastFoundLine);
}
/**
......@@ -741,7 +773,6 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
}
assert mFlexboxHelper.mIndexToFlexLine != null;
int childCount = getChildCount();
View firstView = getChildAt(0);
int currentLineIndex = mFlexboxHelper.mIndexToFlexLine[getPosition(firstView)];
......@@ -1201,26 +1232,11 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
int lastVisibleLinePosition = mFlexboxHelper.mIndexToFlexLine[lastVisiblePosition];
FlexLine lastVisibleLine = mFlexLines.get(lastVisibleLinePosition);
// Loop through the views in the same line of the last visible view because the
// next view should be placed to the end of the flex line to which the last visible view
// belongs
for (int i = getChildCount() - 2, to = getChildCount() - lastVisibleLine.mItemCount - 1;
i > to; i--) {
View viewInSameLine = getChildAt(i);
if (viewInSameLine == null || viewInSameLine.getVisibility() == View.GONE) {
continue;
}
if (mIsRtl && !mainAxisHorizontal) {
// The end edge of the view is left, should be the minimum left edge
// where the next view should be placed
mLayoutState.mOffset = Math.min(mLayoutState.mOffset,
mOrientationHelper.getDecoratedEnd(viewInSameLine));
} else {
mLayoutState.mOffset = Math.max(mLayoutState.mOffset,
mOrientationHelper.getDecoratedEnd(viewInSameLine));
}
}
// The reference view which has the maximum end (or minimum if the layout is RTL and
// the main axis direction is horizontal) coordinate in the last visible flex line.
View referenceView = findLastReferenceViewInLine(lastVisible, lastVisibleLine);
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(referenceView);
mLayoutState.mItemDirection = ItemDirection.TAIL;
mLayoutState.mPosition = lastVisiblePosition + mLayoutState.mItemDirection;
if (mFlexboxHelper.mIndexToFlexLine.length <= mLayoutState.mPosition) {
......@@ -1229,7 +1245,7 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
mLayoutState.mFlexLinePosition
= mFlexboxHelper.mIndexToFlexLine[mLayoutState.mPosition];
}
mLayoutState.mScrollingOffset = mOrientationHelper.getDecoratedEnd(lastVisible)
mLayoutState.mScrollingOffset = mOrientationHelper.getDecoratedEnd(referenceView)
- mOrientationHelper.getEndAfterPadding();
// If the RecyclerView tries to scroll beyond the already calculated
......@@ -1260,26 +1276,17 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
}
} else {
View firstVisible = getChildAt(0);
mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(firstVisible);
int firstVisiblePosition = getPosition(firstVisible);
int firstVisibleLinePosition = mFlexboxHelper.mIndexToFlexLine[firstVisiblePosition];
FlexLine firstVisibleLine = mFlexLines.get(firstVisibleLinePosition);
// Loop through the views in the same line of the first visible view because the
// next view should be placed to the start of the flex line to which the first visible
// view belongs
for (int i = 1, to = firstVisibleLine.mItemCount;
i < to; i++) {
View viewInSameLine = getChildAt(i);
if (mIsRtl && !mainAxisHorizontal) {
mLayoutState.mOffset = Math.max(mLayoutState.mOffset,
mOrientationHelper.getDecoratedStart(viewInSameLine));
} else {
mLayoutState.mOffset = Math.min(mLayoutState.mOffset,
mOrientationHelper.getDecoratedStart(viewInSameLine));
}
}
// The reference view which has the minimum start (or maximum if the layout is RTL and
// the main axis direction is horizontal) coordinate in the first visible flex line
View referenceView = findFirstReferenceViewInLine(firstVisible, firstVisibleLine);
mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(referenceView);
mLayoutState.mItemDirection = ItemDirection.TAIL;
int flexLinePosition = mFlexboxHelper.mIndexToFlexLine[firstVisiblePosition];
if (flexLinePosition == NO_POSITION) {
......@@ -1290,14 +1297,79 @@ public class FlexboxLayoutManager extends RecyclerView.LayoutManager implements
// shifting the position by the number of the items in the current line.
mLayoutState.mPosition = firstVisiblePosition - currentLine.getItemCount();
mLayoutState.mFlexLinePosition = flexLinePosition > 0 ? flexLinePosition - 1 : 0;
mLayoutState.mScrollingOffset = -mOrientationHelper.getDecoratedStart(firstVisible)
mLayoutState.mScrollingOffset = -mOrientationHelper.getDecoratedStart(referenceView)
+ mOrientationHelper.getStartAfterPadding();
}
mLayoutState.mAvailable = absDelta - mLayoutState.mScrollingOffset;
}
/**
* Copied from {@link android.support.v7.widget.RecyclerView.LayoutManager#shouldMeasureChild(View,
* Loop through the first visible flex line to find the reference view, which has the minimum
* start (or maximum if the layout is RTL and main axis direction is horizontal) coordinate.
* @param firstView the first visible view
* @param firstVisibleLine the first visible flex line
* @return the reference view
*/
private View findFirstReferenceViewInLine(View firstView, FlexLine firstVisibleLine) {
boolean mainAxisHorizontal = isMainAxisDirectionHorizontal();
View referenceView = firstView;
for (int i = 1, to = firstVisibleLine.mItemCount;
i < to; i++) {
View viewInSameLine = getChildAt(i);
if (viewInSameLine == null || viewInSameLine.getVisibility() == View.GONE) {
continue;
}
if (mIsRtl && !mainAxisHorizontal) {
if (mOrientationHelper.getDecoratedStart(referenceView)
< mOrientationHelper.getDecoratedStart(viewInSameLine)) {
referenceView = viewInSameLine;
}
} else {
if (mOrientationHelper.getDecoratedStart(referenceView)
> mOrientationHelper.getDecoratedStart(viewInSameLine)) {
referenceView = viewInSameLine;
}
}
}
return referenceView;
}
/**
* Loop through the last visible flex line to find the reference view, which has the maximum
* end (or minimum if the layout is RTL and main axis direction is horizontal) coordinate.
* @param lastView the last visible view
* @param lastVisibleLine the last visible flex line
* @return the reference view
*/
private View findLastReferenceViewInLine(View lastView, FlexLine lastVisibleLine) {
boolean mainAxisHorizontal = isMainAxisDirectionHorizontal();
View referenceView = lastView;
for (int i = getChildCount() - 2, to = getChildCount() - lastVisibleLine.mItemCount - 1;
i > to; i--) {
View viewInSameLine = getChildAt(i);
if (viewInSameLine == null || viewInSameLine.getVisibility() == View.GONE) {
continue;
}
if (mIsRtl && !mainAxisHorizontal) {
// The end edge of the view is left, should be the minimum left edge
// where the next view should be placed
if (mOrientationHelper.getDecoratedEnd(referenceView) >
mOrientationHelper.getDecoratedEnd(viewInSameLine)) {
referenceView = viewInSameLine;
}
} else {
if (mOrientationHelper.getDecoratedEnd(referenceView) <
mOrientationHelper.getDecoratedEnd(viewInSameLine)) {
referenceView = viewInSameLine;
}
}
}
return referenceView;
}
/**
* Copied from {@link android.support.v7.widget.RecyclerView.LayoutManager#shouldMeasureChild
* (View,
* int, int, RecyclerView.LayoutParams)}}
*/
private boolean shouldMeasureChild(View child, int widthSpec, int heightSpec,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册