Skip to content

Commit e171891

Browse files
committed
Fixes the issue that the index of the view becomes inconsistent on the (#311)
conditions that - Adapter has multiple viewTypes (1, 2. Assuming 2 is the special viewtype) - A new item is inserted to the adpater before the view which has the special viewType - At the time item is inserted, the special view is not visible. In that case, the index of the view which has the special viewType is shifted by the number of items inserted before the position.
1 parent f7e5452 commit e171891

File tree

5 files changed

+245
-84
lines changed

5 files changed

+245
-84
lines changed

flexbox/src/androidTest/java/com/google/android/flexbox/test/FlexboxLayoutManagerTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import static org.hamcrest.Matchers.instanceOf;
2929
import static org.hamcrest.Matchers.is;
30+
import static org.hamcrest.Matchers.lessThan;
3031
import static org.hamcrest.Matchers.notNullValue;
3132
import static org.hamcrest.core.IsNot.not;
3233
import static org.junit.Assert.assertThat;
@@ -3194,6 +3195,79 @@ public void run() {
31943195
isEqualAllowingError(TestUtil.dpToPixel(activity, 120)));
31953196
}
31963197

3198+
@Test
3199+
@FlakyTest
3200+
public void testDrawDirtyFlexLine_multi_viewTypes_direction_row() throws Throwable {
3201+
// This test verifies https://github.com/google/flexbox-layout/issues/280
3202+
// the position of the view type is shifted if a new item is inserted before the
3203+
// view which has the special viewType and that view isn't visible at the time the item
3204+
// was inserted.
3205+
final FlexboxTestActivity activity = mActivityRule.getActivity();
3206+
final FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(activity);
3207+
final TestAdapterMultiViewTypes adapter = new TestAdapterMultiViewTypes();
3208+
mActivityRule.runOnUiThread(new Runnable() {
3209+
@Override
3210+
public void run() {
3211+
activity.setContentView(R.layout.recyclerview);
3212+
RecyclerView recyclerView = (RecyclerView) activity.findViewById(R.id.recyclerview);
3213+
layoutManager.setFlexDirection(FlexDirection.ROW);
3214+
recyclerView.setLayoutManager(layoutManager);
3215+
recyclerView.setAdapter(adapter);
3216+
}
3217+
});
3218+
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
3219+
RecyclerView parent = (RecyclerView) activity.findViewById(R.id.recyclerview);
3220+
TextView matchParentText = (TextView) layoutManager
3221+
.getChildAt(TestAdapterMultiViewTypes.POSITION_MATCH_PARENT);
3222+
assertThat(matchParentText.getWidth(), is(parent.getWidth()));
3223+
assertThat(matchParentText.getText().toString(),
3224+
is(String.valueOf(TestAdapterMultiViewTypes.POSITION_MATCH_PARENT + 1)));
3225+
3226+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
3227+
GeneralLocation.TOP_CENTER));
3228+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
3229+
GeneralLocation.TOP_CENTER));
3230+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
3231+
GeneralLocation.TOP_CENTER));
3232+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.BOTTOM_CENTER,
3233+
GeneralLocation.TOP_CENTER));
3234+
3235+
final int insertedValue = 10;
3236+
mActivityRule.runOnUiThread(new Runnable() {
3237+
@Override
3238+
public void run() {
3239+
TestAdapterMultiViewTypes.Item item = new TestAdapterMultiViewTypes.Item();
3240+
item.value = insertedValue;
3241+
// Insert an item before the position that has a special viewType
3242+
adapter.addItemAt(TestAdapterMultiViewTypes.POSITION_MATCH_PARENT - 1, item);
3243+
}
3244+
});
3245+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
3246+
GeneralLocation.BOTTOM_CENTER));
3247+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
3248+
GeneralLocation.BOTTOM_CENTER));
3249+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
3250+
GeneralLocation.BOTTOM_CENTER));
3251+
onView(withId(R.id.recyclerview)).perform(swipe(GeneralLocation.TOP_CENTER,
3252+
GeneralLocation.BOTTOM_CENTER));
3253+
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
3254+
3255+
// Since a new item is inserted before the position, the index at the view who has the
3256+
// special viewType should be shifted.
3257+
matchParentText = (TextView) layoutManager
3258+
.getChildAt(TestAdapterMultiViewTypes.POSITION_MATCH_PARENT + 1);
3259+
assertThat(matchParentText.getWidth(), is(parent.getWidth()));
3260+
assertThat(matchParentText.getText().toString(),
3261+
is(String.valueOf(TestAdapterMultiViewTypes.POSITION_MATCH_PARENT + 1)));
3262+
3263+
// The position of this view is the old position who had the special viewType, but
3264+
// now the viewType should be a normal one
3265+
TextView textView = (TextView) layoutManager
3266+
.getChildAt(TestAdapterMultiViewTypes.POSITION_MATCH_PARENT - 1);
3267+
assertThat(textView.getWidth(), lessThan(parent.getWidth()));
3268+
assertThat(textView.getText().toString(), is(String.valueOf(insertedValue)));
3269+
}
3270+
31973271
@Test
31983272
@FlakyTest
31993273
public void testChildrenSizeWithMargin() throws Throwable {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2017 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.flexbox.test;
18+
19+
import android.support.v7.widget.RecyclerView;
20+
import android.view.Gravity;
21+
import android.view.LayoutInflater;
22+
import android.view.View;
23+
import android.view.ViewGroup;
24+
25+
import com.google.android.flexbox.FlexboxLayoutManager;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
/**
31+
* {@link RecyclerView.Adapter} implementation for {@link TestViewHolder}, which has multiple
32+
* view types.
33+
*/
34+
class TestAdapterMultiViewTypes extends RecyclerView.Adapter<TestViewHolder> {
35+
36+
static final int POSITION_MATCH_PARENT = 3;
37+
private static final int ITEMS = 50;
38+
39+
private static final int VIEW_TYPE_NORMAL = 0;
40+
private static final int VIEW_TYPE_MATCH_PARENT = 1;
41+
42+
private final List<Item> mItems;
43+
44+
TestAdapterMultiViewTypes() {
45+
mItems = new ArrayList<>();
46+
for (int i = 0; i < ITEMS; i++) {
47+
Item item = new Item();
48+
if (i == POSITION_MATCH_PARENT) {
49+
item.viewType = VIEW_TYPE_MATCH_PARENT;
50+
}
51+
item.value = i + 1;
52+
mItems.add(item);
53+
}
54+
}
55+
56+
@Override
57+
public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
58+
View view = LayoutInflater.from(parent.getContext())
59+
.inflate(R.layout.recyclerview_viewholder, parent, false);
60+
if (viewType == VIEW_TYPE_MATCH_PARENT) {
61+
FlexboxLayoutManager.LayoutParams flexboxLp = (FlexboxLayoutManager.LayoutParams)
62+
view.getLayoutParams();
63+
flexboxLp.setFlexBasisPercent(90f);
64+
flexboxLp.setFlexGrow(1f);
65+
}
66+
return new TestViewHolder(view);
67+
}
68+
69+
@Override
70+
public void onBindViewHolder(TestViewHolder holder, int position) {
71+
holder.mTextView.setText(String.valueOf(mItems.get(position).value));
72+
holder.mTextView.setBackgroundResource(R.drawable.flex_item_background);
73+
holder.mTextView.setGravity(Gravity.CENTER);
74+
}
75+
76+
@Override
77+
public int getItemViewType(int position) {
78+
return mItems.get(position).viewType;
79+
}
80+
81+
void addItemAt(int position, Item item) {
82+
mItems.add(position, item);
83+
notifyItemInserted(position);
84+
}
85+
86+
Item getItemAt(int position) {
87+
return mItems.get(position);
88+
}
89+
90+
@Override
91+
public int getItemCount() {
92+
return mItems.size();
93+
}
94+
95+
static class Item {
96+
int viewType = VIEW_TYPE_NORMAL;
97+
int value;
98+
}
99+
}

flexbox/src/main/java/com/google/android/flexbox/FlexLine.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,6 @@ public class FlexLine {
9494

9595
int mLastIndex;
9696

97-
/**
98-
* Indicates whether this flex line instance is in a dirty state (needs to be computed before
99-
* rendering). This flag is set to true for example an item is added at the position not
100-
* visible and the position is prior to the position than the first visible position.
101-
* In that case, the newly added item and flex line needs to be re-computed when the
102-
* RecyclerView is scrolled toward start and it's about to be drawn.
103-
*
104-
* Note that a re-computation is not needed for the case where the dirty flag is set to a
105-
* flex line after the last visible position. Because the flex lines after the visible potion
106-
* are going to be cleared in the normal layout pass in the onLayoutChildren -> updateFlexLine
107-
* method. Thus at the time scrolling to the added position, new flex lines will be computed
108-
* anyway.
109-
*/
110-
boolean mDirty;
111-
11297
/**
11398
* @return the distance in pixels from the top edge of this view's parent
11499
* to the top edge of this FlexLine.

flexbox/src/main/java/com/google/android/flexbox/FlexboxHelper.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ FlexLinesResult calculateVerticalFlexLinesToIndex(int widthMeasureSpec, int heig
362362
* @return an instance of {@link FlexLinesResult} that contains a list of flex lines and the
363363
* child state used by {@link View#setMeasuredDimension(int, int)}.
364364
*/
365-
private FlexLinesResult calculateFlexLines(int mainMeasureSpec, int crossMeasureSpec,
365+
FlexLinesResult calculateFlexLines(int mainMeasureSpec, int crossMeasureSpec,
366366
int needsCalcAmount, int fromIndex, int toIndex,
367367
@Nullable List<FlexLine> existingLines) {
368368

@@ -825,7 +825,6 @@ private void addFlexLine(List<FlexLine> flexLines, FlexLine flexLine, int viewIn
825825
flexLine.mSumCrossSizeBefore = usedCrossSizeSoFar;
826826
mFlexContainer.onNewFlexLineAdded(flexLine);
827827
flexLine.mLastIndex = viewIndex;
828-
flexLine.mDirty = false;
829828
flexLines.add(flexLine);
830829
}
831830

@@ -1556,6 +1555,9 @@ void stretchViews(int fromIndex) {
15561555
FlexLine flexLine = flexLines.get(i);
15571556
for (int j = 0, itemCount = flexLine.mItemCount; j < itemCount;
15581557
j++, viewIndex++) {
1558+
if (j >= mFlexContainer.getFlexItemCount()) {
1559+
continue;
1560+
}
15591561
View view = mFlexContainer.getReorderedFlexItemAt(viewIndex);
15601562
if (view == null || view.getVisibility() == View.GONE) {
15611563
continue;

0 commit comments

Comments
 (0)