Skip to content

Commit 4dc2ba6

Browse files
author
Rafael Dominiquini
committed
Support GridLayoutManager
1 parent 728994d commit 4dc2ba6

File tree

5 files changed

+110
-38
lines changed

5 files changed

+110
-38
lines changed

library/src/main/java/com/timehop/stickyheadersrecyclerview/HeaderPositionCalculator.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ public class HeaderPositionCalculator {
2828
private final Rect mTempRect1 = new Rect();
2929
private final Rect mTempRect2 = new Rect();
3030

31-
public HeaderPositionCalculator(StickyRecyclerHeadersAdapter adapter, HeaderProvider headerProvider,
32-
OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator) {
31+
public HeaderPositionCalculator(StickyRecyclerHeadersAdapter adapter, HeaderProvider headerProvider, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator) {
3332
mAdapter = adapter;
3433
mHeaderProvider = headerProvider;
3534
mOrientationProvider = orientationProvider;
@@ -74,6 +73,13 @@ public boolean hasNewHeader(int position, boolean isReverseLayout) {
7473
return false;
7574
}
7675

76+
int numColumns = mAdapter.getNumColumns();
77+
int columnOfItem = position % numColumns;
78+
if (columnOfItem > 0) {
79+
int firstItemOnRowPosition = position - columnOfItem;
80+
return hasNewHeader(firstItemOnRowPosition, isReverseLayout);
81+
}
82+
7783
long headerId = mAdapter.getHeaderId(position);
7884

7985
if (headerId < 0) {

library/src/main/java/com/timehop/stickyheadersrecyclerview/StickyRecyclerHeadersAdapter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,9 @@ public interface StickyRecyclerHeadersAdapter<VH extends RecyclerView.ViewHolder
3737
* @return the number of views in the adapter
3838
*/
3939
int getItemCount();
40+
41+
/**
42+
* @return the number of columns
43+
*/
44+
int getNumColumns();
4045
}

library/src/main/java/com/timehop/stickyheadersrecyclerview/decorators/StickyRecyclerHeadersDecoration.java

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
public class StickyRecyclerHeadersDecoration extends RecyclerView.ItemDecoration {
2222

2323
private final StickyRecyclerHeadersAdapter mAdapter;
24-
private final ItemVisibilityAdapter mVisibilityAdapter;
2524
private final SparseArray<Rect> mHeaderRects = new SparseArray<>();
2625
private final HeaderProvider mHeaderProvider;
2726
private final OrientationProvider mOrientationProvider;
@@ -30,6 +29,7 @@ public class StickyRecyclerHeadersDecoration extends RecyclerView.ItemDecoration
3029
private final DimensionCalculator mDimensionCalculator;
3130
private final boolean mEnableStickyHeader;
3231

32+
private ItemVisibilityAdapter mVisibilityAdapter;
3333
private StickyRecyclerHeadersPositionChangeListener mHeaderListener;
3434

3535
/**
@@ -39,40 +39,28 @@ public class StickyRecyclerHeadersDecoration extends RecyclerView.ItemDecoration
3939
private final Rect mTempRect = new Rect();
4040

4141
public StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter) {
42-
this(adapter, new LinearLayoutOrientationProvider(), new DimensionCalculator(), null, true);
42+
this(adapter, new LinearLayoutOrientationProvider(), new DimensionCalculator(), true);
4343
}
4444

4545
public StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, boolean enableStickyHeader) {
46-
this(adapter, new LinearLayoutOrientationProvider(), new DimensionCalculator(), null, enableStickyHeader);
46+
this(adapter, new LinearLayoutOrientationProvider(), new DimensionCalculator(), enableStickyHeader);
4747
}
4848

49-
public StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, ItemVisibilityAdapter visibilityAdapter, boolean enableStickyHeader) {
50-
this(adapter, new LinearLayoutOrientationProvider(), new DimensionCalculator(), visibilityAdapter, enableStickyHeader);
49+
private StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, boolean enableStickyHeader) {
50+
this(adapter, orientationProvider, dimensionCalculator, new HeaderRenderer(orientationProvider), new HeaderViewCache(adapter, orientationProvider), enableStickyHeader);
5151
}
5252

53-
private StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, OrientationProvider orientationProvider,
54-
DimensionCalculator dimensionCalculator, ItemVisibilityAdapter visibilityAdapter, boolean enableStickyHeader) {
55-
this(adapter, orientationProvider, dimensionCalculator, new HeaderRenderer(orientationProvider),
56-
new HeaderViewCache(adapter, orientationProvider), visibilityAdapter, enableStickyHeader);
53+
private StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, HeaderRenderer headerRenderer, HeaderProvider headerProvider, boolean enableStickyHeader) {
54+
this(adapter, headerRenderer, orientationProvider, dimensionCalculator, headerProvider, new HeaderPositionCalculator(adapter, headerProvider, orientationProvider, dimensionCalculator), enableStickyHeader);
5755
}
5856

59-
private StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, OrientationProvider orientationProvider,
60-
DimensionCalculator dimensionCalculator, HeaderRenderer headerRenderer, HeaderProvider headerProvider, ItemVisibilityAdapter visibilityAdapter, boolean enableStickyHeader) {
61-
this(adapter, headerRenderer, orientationProvider, dimensionCalculator, headerProvider,
62-
new HeaderPositionCalculator(adapter, headerProvider, orientationProvider,
63-
dimensionCalculator), visibilityAdapter, enableStickyHeader);
64-
}
65-
66-
private StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, HeaderRenderer headerRenderer,
67-
OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, HeaderProvider headerProvider,
68-
HeaderPositionCalculator headerPositionCalculator, ItemVisibilityAdapter visibilityAdapter, boolean enableStickyHeader) {
57+
private StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter, HeaderRenderer headerRenderer, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, HeaderProvider headerProvider, HeaderPositionCalculator headerPositionCalculator, boolean enableStickyHeader) {
6958
mAdapter = adapter;
7059
mHeaderProvider = headerProvider;
7160
mOrientationProvider = orientationProvider;
7261
mRenderer = headerRenderer;
7362
mDimensionCalculator = dimensionCalculator;
7463
mHeaderPositionCalculator = headerPositionCalculator;
75-
mVisibilityAdapter = visibilityAdapter;
7664
mEnableStickyHeader = enableStickyHeader;
7765
}
7866

@@ -116,20 +104,24 @@ public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State st
116104

117105
for (int i = 0; i < childCount; i++) {
118106
View itemView = parent.getChildAt(i);
107+
119108
int position = parent.getChildAdapterPosition(itemView);
120-
if (position == RecyclerView.NO_POSITION) {
109+
110+
if (position == RecyclerView.NO_POSITION || position % mAdapter.getNumColumns() > 0) {
121111
continue;
122112
}
123113

124114
boolean hasStickyHeader = mHeaderPositionCalculator.hasStickyHeader(itemView, mOrientationProvider.getOrientation(parent), position);
125115
if (hasStickyHeader || mHeaderPositionCalculator.hasNewHeader(position, mOrientationProvider.isReverseLayout(parent))) {
126116
View header = mHeaderProvider.getHeader(parent, position);
117+
127118
//re-use existing Rect, if any.
128119
Rect headerOffset = mHeaderRects.get(position);
129120
if (headerOffset == null) {
130121
headerOffset = new Rect();
131122
mHeaderRects.put(position, headerOffset);
132123
}
124+
133125
mHeaderPositionCalculator.initHeaderBounds(headerOffset, parent, header, itemView, hasStickyHeader, mEnableStickyHeader);
134126
mRenderer.drawHeader(parent, canvas, header, headerOffset);
135127

@@ -191,6 +183,10 @@ public void invalidateHeaders() {
191183
mHeaderRects.clear();
192184
}
193185

186+
public void setVisibilityAdapter(ItemVisibilityAdapter visibilityAdapter) {
187+
this.mVisibilityAdapter = visibilityAdapter;
188+
}
189+
194190
public void setHeaderPositionListener(StickyRecyclerHeadersPositionChangeListener headerListener) {
195191
this.mHeaderListener = headerListener;
196192
}

sample/src/main/java/com/timehop/stickyheadersrecyclerview/sample/AnimalsAdapter.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,31 @@
1010
/**
1111
* Adapter holding a list of animal names of type String. Note that each item must be unique.
1212
*/
13-
public abstract class AnimalsAdapter<VH extends RecyclerView.ViewHolder>
14-
extends RecyclerView.Adapter<VH> {
15-
private ArrayList<String> items = new ArrayList<String>();
13+
public abstract class AnimalsAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
14+
15+
protected ArrayList<String> items = new ArrayList<String>();
1616

1717
public AnimalsAdapter() {
1818
setHasStableIds(true);
1919
}
2020

2121
public void add(String object) {
2222
items.add(object);
23-
notifyDataSetChanged();
23+
24+
updateAdapter();
2425
}
2526

2627
public void add(int index, String object) {
2728
items.add(index, object);
28-
notifyDataSetChanged();
29+
30+
updateAdapter();
2931
}
3032

3133
public void addAll(Collection<? extends String> collection) {
3234
if (collection != null) {
3335
items.addAll(collection);
34-
notifyDataSetChanged();
36+
37+
updateAdapter();
3538
}
3639
}
3740

@@ -41,12 +44,14 @@ public void addAll(String... items) {
4144

4245
public void clear() {
4346
items.clear();
44-
notifyDataSetChanged();
47+
48+
updateAdapter();
4549
}
4650

4751
public void remove(String object) {
4852
items.remove(object);
49-
notifyDataSetChanged();
53+
54+
updateAdapter();
5055
}
5156

5257
public String getItem(int position) {
@@ -62,4 +67,8 @@ public long getItemId(int position) {
6267
public int getItemCount() {
6368
return items.size();
6469
}
70+
71+
public void updateAdapter() {
72+
super.notifyDataSetChanged();
73+
}
6574
}

sample/src/main/java/com/timehop/stickyheadersrecyclerview/sample/MainActivity.java

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import android.support.v7.widget.GridLayoutManager;
1212
import android.support.v7.widget.LinearLayoutManager;
1313
import android.support.v7.widget.RecyclerView;
14+
import android.text.TextUtils;
1415
import android.util.Log;
1516
import android.view.LayoutInflater;
1617
import android.view.View;
@@ -29,11 +30,12 @@
2930

3031
public class MainActivity extends AppCompatActivity {
3132

32-
public static final int NUM_COLUMNS = 1;
33+
public static final int NUM_COLUMNS = 2;
3334

3435
@Override
3536
protected void onCreate(Bundle savedInstanceState) {
3637
super.onCreate(savedInstanceState);
38+
3739
setContentView(R.layout.activity_main);
3840

3941
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
@@ -42,7 +44,6 @@ protected void onCreate(Bundle savedInstanceState) {
4244

4345
// Set adapter populated with example dummy data
4446
final AnimalsHeadersAdapter adapter = new AnimalsHeadersAdapter();
45-
adapter.add("List of Animals");
4647
adapter.addAll(getDummyDataSet());
4748
recyclerView.setAdapter(adapter);
4849

@@ -51,8 +52,10 @@ protected void onCreate(Bundle savedInstanceState) {
5152
@Override
5253
public void onClick(View v) {
5354
Handler handler = new Handler(Looper.getMainLooper());
55+
5456
for (int i = 0; i < adapter.getItemCount(); i++) {
5557
final int index = i;
58+
5659
handler.postDelayed(new Runnable() {
5760
@Override
5861
public void run() {
@@ -135,8 +138,10 @@ private int getLayoutManagerOrientation(int activityOrientation) {
135138
}
136139
}
137140

138-
private class AnimalsHeadersAdapter extends AnimalsAdapter<RecyclerView.ViewHolder>
139-
implements StickyRecyclerHeadersAdapter<RecyclerView.ViewHolder> {
141+
private class AnimalsHeadersAdapter extends AnimalsAdapter<RecyclerView.ViewHolder> implements StickyRecyclerHeadersAdapter<RecyclerView.ViewHolder> {
142+
143+
private static final String EMPTY_NAME = " ";
144+
140145
@Override
141146
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
142147
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
@@ -152,10 +157,11 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
152157

153158
@Override
154159
public long getHeaderId(int position) {
155-
if (position == 0) {
156-
return -1;
160+
int numColumnOfItem = position % getNumColumns();
161+
if (numColumnOfItem == 0) {
162+
return getFirstChar(getItem(position));
157163
} else {
158-
return getItem(position).charAt(0);
164+
return getHeaderId(position - numColumnOfItem);
159165
}
160166
}
161167

@@ -174,11 +180,61 @@ public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position)
174180
holder.itemView.setBackgroundColor(getRandomColor());
175181
}
176182

183+
@Override
184+
public int getNumColumns() {
185+
return NUM_COLUMNS;
186+
}
187+
188+
@Override
189+
public void updateAdapter() {
190+
reorderItems();
191+
192+
super.updateAdapter();
193+
}
194+
177195
private int getRandomColor() {
178196
SecureRandom rgen = new SecureRandom();
179197
return Color.HSVToColor(150, new float[]{
180198
rgen.nextInt(359), 1, 1
181199
});
182200
}
201+
202+
private void reorderItems() {
203+
long firstCharOnLastItem = -1;
204+
205+
for (int i = 0; i < items.size(); i++) {
206+
String item = items.get(i);
207+
if (getFirstChar(item) != firstCharOnLastItem) { // new header found for item
208+
int numColumnOfItem = i % getNumColumns();
209+
if (numColumnOfItem > 0 && !EMPTY_NAME.equals(item)) { // fill row with empty items
210+
int emptyVideos = getNumColumns() - numColumnOfItem;
211+
for (int j = 0; j < emptyVideos; j++) {
212+
items.add(i, EMPTY_NAME);
213+
if (j != emptyVideos - 1) {
214+
i++;
215+
}
216+
}
217+
continue;
218+
} else if (numColumnOfItem == 0 && EMPTY_NAME.equals(item)){
219+
// remove empty items to avoid empty rows when removing items
220+
while (items.get(i).equals(EMPTY_NAME)) {
221+
items.remove(i);
222+
}
223+
i--;
224+
}
225+
if (!EMPTY_NAME.equals(item)) {
226+
firstCharOnLastItem = getFirstChar(item);
227+
}
228+
}
229+
}
230+
}
231+
232+
private int getFirstChar(String name) {
233+
if (TextUtils.isEmpty(name)) {
234+
return 0;
235+
} else {
236+
return name.charAt(0);
237+
}
238+
}
183239
}
184240
}

0 commit comments

Comments
 (0)