Skip to content

Commit 387c24b

Browse files
author
Rafael Dominiquini
committed
Fixed bug when detecting clicks on sticky headers
1 parent 64ee47b commit 387c24b

File tree

5 files changed

+103
-92
lines changed

5 files changed

+103
-92
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ ext {
2424
supportLibsVersion = '23.3.0'
2525
targetSdkVersion = 23
2626
minSdkVersion = 11
27-
versionCode = 16
28-
versionName = "0.5.6"
27+
versionCode = 18
28+
versionName = "0.5.8"
2929
}

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

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@ public class HeaderPositionCalculator {
2121
private final HeaderProvider mHeaderProvider;
2222
private final DimensionCalculator mDimensionCalculator;
2323

24-
/**
25-
* The following fields are used as buffers for internal calculations. Their sole purpose is to avoid
26-
* allocating new Rect every time we need one.
27-
*/
28-
private final Rect mTempRect1 = new Rect();
29-
private final Rect mTempRect2 = new Rect();
30-
3124
public HeaderPositionCalculator(StickyRecyclerHeadersAdapter adapter, HeaderProvider headerProvider, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator) {
3225
mAdapter = adapter;
3326
mHeaderProvider = headerProvider;
@@ -49,15 +42,15 @@ public HeaderPositionCalculator(StickyRecyclerHeadersAdapter adapter, HeaderProv
4942
public boolean hasStickyHeader(View itemView, int orientation, int position) {
5043
int offset, margin;
5144

52-
mDimensionCalculator.initMargins(mTempRect1, itemView);
53-
5445
if (itemView != null) {
46+
Rect rect = mDimensionCalculator.getMargins(itemView);
47+
5548
if (orientation == LinearLayout.VERTICAL) {
5649
offset = itemView.getTop();
57-
margin = mTempRect1.top;
50+
margin = rect.top;
5851
} else {
5952
offset = itemView.getLeft();
60-
margin = mTempRect1.left;
53+
margin = rect.left;
6154
}
6255
} else {
6356
offset = -1;
@@ -127,30 +120,25 @@ public boolean headerObscuringSomeItem(RecyclerView parent, View firstHeader) {
127120
return false;
128121
}
129122

130-
public void initHeaderBounds(Rect bounds, RecyclerView recyclerView, View header, View firstView, boolean firstHeader, boolean enableStickyHeader) {
123+
public Rect getHeaderBounds(RecyclerView recyclerView, View header, View firstView, boolean firstHeader, boolean enableStickyHeader) {
131124
int orientation = mOrientationProvider.getOrientation(recyclerView);
132-
initDefaultHeaderOffset(bounds, recyclerView, header, firstView, orientation, enableStickyHeader);
125+
126+
Rect bounds = getDefaultHeaderOffset(recyclerView, header, firstView, orientation, enableStickyHeader);
133127

134128
if (enableStickyHeader && firstHeader && isStickyHeaderBeingPushedOffscreen(recyclerView, header)) {
135129
View viewAfterNextHeader = getFirstViewUnobscuredByHeader(recyclerView, header);
136130
int firstViewUnderHeaderPosition = recyclerView.getChildAdapterPosition(viewAfterNextHeader);
137131
View secondHeader = mHeaderProvider.getHeader(recyclerView, firstViewUnderHeaderPosition);
138132
translateHeaderWithNextHeader(recyclerView, mOrientationProvider.getOrientation(recyclerView), bounds, header, viewAfterNextHeader, secondHeader);
139133
}
140-
}
141-
142-
public Rect getHeaderBounds(RecyclerView recyclerView, View header, View firstView, boolean firstHeader, boolean enableStickyHeader) {
143-
Rect rect = new Rect();
144-
145-
initHeaderBounds(rect, recyclerView, header, firstView, firstHeader, enableStickyHeader);
146134

147-
return rect;
135+
return bounds;
148136
}
149137

150-
private void initDefaultHeaderOffset(Rect headerMargins, RecyclerView recyclerView, View header, View firstView, int orientation, boolean enableStickyHeader) {
138+
private Rect getDefaultHeaderOffset(RecyclerView recyclerView, View header, View firstView, int orientation, boolean enableStickyHeader) {
151139
int translationX, translationY;
152140

153-
mDimensionCalculator.initMargins(mTempRect1, header);
141+
Rect headerMargins = mDimensionCalculator.getMargins(header);
154142

155143
if (header != null && firstView != null) {
156144
ViewGroup.LayoutParams layoutParams = firstView.getLayoutParams();
@@ -163,18 +151,20 @@ private void initDefaultHeaderOffset(Rect headerMargins, RecyclerView recyclerVi
163151
}
164152

165153
if (orientation == LinearLayoutManager.VERTICAL) {
166-
translationX = firstView.getLeft() - leftMargin + mTempRect1.left;
154+
translationX = firstView.getLeft() - leftMargin + headerMargins.left;
167155
translationY = enableStickyHeader ?
168-
Math.max(firstView.getTop() - topMargin - header.getHeight() - mTempRect1.bottom, getListTop(recyclerView) + mTempRect1.top) :
169-
firstView.getTop() - topMargin - header.getHeight() - mTempRect1.bottom;
156+
Math.max(firstView.getTop() - topMargin - header.getHeight() - headerMargins.bottom, getListTop(recyclerView) + headerMargins.top) :
157+
firstView.getTop() - topMargin - header.getHeight() - headerMargins.bottom;
170158
} else {
171-
translationY = firstView.getTop() - topMargin + mTempRect1.top;
159+
translationY = firstView.getTop() - topMargin + headerMargins.top;
172160
translationX = enableStickyHeader ?
173-
Math.max(firstView.getLeft() - leftMargin - header.getWidth() - mTempRect1.right, getListLeft(recyclerView) + mTempRect1.left) :
174-
firstView.getLeft() - leftMargin - header.getWidth() - mTempRect1.right;
161+
Math.max(firstView.getLeft() - leftMargin - header.getWidth() - headerMargins.right, getListLeft(recyclerView) + headerMargins.left) :
162+
firstView.getLeft() - leftMargin - header.getWidth() - headerMargins.right;
175163
}
176164

177-
headerMargins.set(translationX, translationY, translationX + header.getWidth(), translationY + header.getHeight());
165+
return new Rect(translationX, translationY, translationX + header.getWidth(), translationY + header.getHeight());
166+
} else {
167+
return headerMargins;
178168
}
179169
}
180170

@@ -189,18 +179,18 @@ private boolean isStickyHeaderBeingPushedOffscreen(RecyclerView recyclerView, Vi
189179
boolean isReverseLayout = mOrientationProvider.isReverseLayout(recyclerView);
190180
if (firstViewUnderHeaderPosition > 0 && hasNewHeader(firstViewUnderHeaderPosition, isReverseLayout)) {
191181
View nextHeader = mHeaderProvider.getHeader(recyclerView, firstViewUnderHeaderPosition);
192-
mDimensionCalculator.initMargins(mTempRect1, nextHeader);
193-
mDimensionCalculator.initMargins(mTempRect2, stickyHeader);
182+
Rect nextHeaderMargins = mDimensionCalculator.getMargins(nextHeader);
183+
Rect headerMargins = mDimensionCalculator.getMargins(stickyHeader);
194184

195185
if (mOrientationProvider.getOrientation(recyclerView) == LinearLayoutManager.VERTICAL) {
196-
int topOfNextHeader = viewAfterHeader.getTop() - mTempRect1.bottom - nextHeader.getHeight() - mTempRect1.top;
197-
int bottomOfThisHeader = recyclerView.getPaddingTop() + stickyHeader.getBottom() + mTempRect2.top + mTempRect2.bottom;
186+
int topOfNextHeader = viewAfterHeader.getTop() - nextHeaderMargins.bottom - nextHeader.getHeight() - nextHeaderMargins.top;
187+
int bottomOfThisHeader = recyclerView.getPaddingTop() + stickyHeader.getBottom() + headerMargins.top + headerMargins.bottom;
198188
if (topOfNextHeader < bottomOfThisHeader) {
199189
return true;
200190
}
201191
} else {
202-
int leftOfNextHeader = viewAfterHeader.getLeft() - mTempRect1.right - nextHeader.getWidth() - mTempRect1.left;
203-
int rightOfThisHeader = recyclerView.getPaddingLeft() + stickyHeader.getRight() + mTempRect2.left + mTempRect2.right;
192+
int leftOfNextHeader = viewAfterHeader.getLeft() - nextHeaderMargins.right - nextHeader.getWidth() - nextHeaderMargins.left;
193+
int rightOfThisHeader = recyclerView.getPaddingLeft() + stickyHeader.getRight() + headerMargins.left + headerMargins.right;
204194
if (leftOfNextHeader < rightOfThisHeader) {
205195
return true;
206196
}
@@ -214,18 +204,18 @@ private boolean isStickyHeaderBeingPushedOffscreen(RecyclerView recyclerView, Vi
214204
private void translateHeaderWithNextHeader(RecyclerView recyclerView, int orientation, Rect translation,
215205
View currentHeader, View viewAfterNextHeader, View nextHeader) {
216206

217-
mDimensionCalculator.initMargins(mTempRect1, nextHeader);
218-
mDimensionCalculator.initMargins(mTempRect2, currentHeader);
207+
Rect nextHeaderMargins = mDimensionCalculator.getMargins(nextHeader);
208+
Rect stickyHeaderMargins = mDimensionCalculator.getMargins(currentHeader);
219209

220210
if (orientation == LinearLayoutManager.VERTICAL) {
221-
int topOfStickyHeader = getListTop(recyclerView) + mTempRect2.top + mTempRect2.bottom;
222-
int shiftFromNextHeader = viewAfterNextHeader.getTop() - nextHeader.getHeight() - mTempRect1.bottom - mTempRect1.top - currentHeader.getHeight() - topOfStickyHeader;
211+
int topOfStickyHeader = getListTop(recyclerView) + stickyHeaderMargins.top + stickyHeaderMargins.bottom;
212+
int shiftFromNextHeader = viewAfterNextHeader.getTop() - nextHeader.getHeight() - nextHeaderMargins.bottom - nextHeaderMargins.top - currentHeader.getHeight() - topOfStickyHeader;
223213
if (shiftFromNextHeader < topOfStickyHeader) {
224214
translation.top += shiftFromNextHeader;
225215
}
226216
} else {
227-
int leftOfStickyHeader = getListLeft(recyclerView) + mTempRect2.left + mTempRect2.right;
228-
int shiftFromNextHeader = viewAfterNextHeader.getLeft() - nextHeader.getWidth() - mTempRect1.right - mTempRect1.left - currentHeader.getWidth() - leftOfStickyHeader;
217+
int leftOfStickyHeader = getListLeft(recyclerView) + stickyHeaderMargins.left + stickyHeaderMargins.right;
218+
int shiftFromNextHeader = viewAfterNextHeader.getLeft() - nextHeader.getWidth() - nextHeaderMargins.right - nextHeaderMargins.left - currentHeader.getWidth() - leftOfStickyHeader;
229219
if (shiftFromNextHeader < leftOfStickyHeader) {
230220
translation.left += shiftFromNextHeader;
231221
}
@@ -263,7 +253,7 @@ private View getFirstViewUnobscuredByHeader(RecyclerView parent, View firstHeade
263253
*/
264254
private boolean itemIsObscuredByHeader(RecyclerView parent, View item, View header, int orientation) {
265255
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) item.getLayoutParams();
266-
mDimensionCalculator.initMargins(mTempRect1, header);
256+
Rect rect = mDimensionCalculator.getMargins(header);
267257

268258
int adapterPosition = parent.getChildAdapterPosition(item);
269259
if (adapterPosition == RecyclerView.NO_POSITION || mHeaderProvider.getHeader(parent, adapterPosition) != header) {
@@ -274,13 +264,13 @@ private boolean itemIsObscuredByHeader(RecyclerView parent, View item, View head
274264

275265
if (orientation == LinearLayoutManager.VERTICAL) {
276266
int itemTop = item.getTop() - layoutParams.topMargin;
277-
int headerBottom = getListTop(parent) + header.getBottom() + mTempRect1.bottom + mTempRect1.top;
267+
int headerBottom = getListTop(parent) + header.getBottom() + rect.bottom + rect.top;
278268
if (itemTop >= headerBottom) {
279269
return false;
280270
}
281271
} else {
282272
int itemLeft = item.getLeft() - layoutParams.leftMargin;
283-
int headerRight = getListLeft(parent) + header.getRight() + mTempRect1.right + mTempRect1.left;
273+
int headerRight = getListLeft(parent) + header.getRight() + rect.right + rect.left;
284274
if (itemLeft >= headerRight) {
285275
return false;
286276
}

library/src/main/java/com/timehop/stickyheadersrecyclerview/calculation/DimensionCalculator.java

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,63 @@
1111
*/
1212
public class DimensionCalculator {
1313

14+
/**
15+
* Returns {@link Rect} representing margins for any view.
16+
*
17+
* @param view for which to get margins
18+
* @return margins for the given view. All 0 if the view does not support margins
19+
*/
20+
public Rect getMargins(View view) {
21+
LayoutParams layoutParams = view != null ? view.getLayoutParams() : null;
22+
23+
if (layoutParams != null && layoutParams instanceof MarginLayoutParams) {
24+
return getMarginRect((MarginLayoutParams) layoutParams);
25+
} else {
26+
return new Rect();
27+
}
28+
}
29+
1430
/**
1531
* Populates {@link Rect} with margins for any view.
1632
*
1733
*
1834
* @param margins rect to populate
1935
* @param view for which to get margins
2036
*/
21-
public void initMargins(Rect margins, View view) {
37+
public Rect initMargins(Rect margins, View view) {
38+
if (margins == null) {
39+
margins = new Rect();
40+
}
41+
2242
LayoutParams layoutParams = view != null ? view.getLayoutParams() : null;
2343

2444
if (layoutParams != null && layoutParams instanceof MarginLayoutParams) {
2545
initMarginRect(margins, (MarginLayoutParams) layoutParams);
2646
} else {
2747
margins.set(0, 0, 0, 0);
2848
}
49+
50+
return margins;
51+
}
52+
53+
/**
54+
* Converts {@link MarginLayoutParams} into a representative {@link Rect}
55+
*
56+
* @param marginLayoutParams margins to convert to a Rect
57+
* @return Rect representing margins, where {@link MarginLayoutParams#leftMargin} is equivalent to
58+
* {@link Rect#left}, etc.
59+
*/
60+
private Rect getMarginRect(MarginLayoutParams marginLayoutParams) {
61+
if (marginLayoutParams != null) {
62+
return new Rect(
63+
marginLayoutParams.leftMargin,
64+
marginLayoutParams.topMargin,
65+
marginLayoutParams.rightMargin,
66+
marginLayoutParams.bottomMargin
67+
);
68+
} else {
69+
return new Rect();
70+
}
2971
}
3072

3173
/**
@@ -35,7 +77,11 @@ public void initMargins(Rect margins, View view) {
3577
* {@link MarginLayoutParams#leftMargin} is equivalent to {@link Rect#left}, etc.
3678
* @param marginLayoutParams margins to populate the Rect with
3779
*/
38-
private void initMarginRect(Rect marginRect, MarginLayoutParams marginLayoutParams) {
80+
private Rect initMarginRect(Rect marginRect, MarginLayoutParams marginLayoutParams) {
81+
if (marginRect == null) {
82+
marginRect = new Rect();
83+
}
84+
3985
if (marginLayoutParams != null) {
4086
marginRect.set(
4187
marginLayoutParams.leftMargin,
@@ -44,17 +90,7 @@ private void initMarginRect(Rect marginRect, MarginLayoutParams marginLayoutPara
4490
marginLayoutParams.bottomMargin
4591
);
4692
}
47-
}
48-
49-
public Rect getMarginRect(View header) {
50-
Rect rect = new Rect();
51-
52-
if (header != null && header.getLayoutParams() instanceof MarginLayoutParams) {
53-
initMarginRect(rect, (MarginLayoutParams) header.getLayoutParams());
54-
} else {
55-
rect.set(0, 0, 0, 0);
56-
}
5793

58-
return rect;
94+
return marginRect;
5995
}
6096
}

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

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,7 @@ public class StickyRecyclerHeadersDecoration extends RecyclerView.ItemDecoration
3131
private ItemVisibilityAdapter mVisibilityAdapter;
3232
private StickyRecyclerHeadersPositionChangeListener mHeaderListener;
3333

34-
/**
35-
* The following field is used as a buffer for internal calculations. Its sole purpose is to avoid
36-
* allocating new Rect every time we need one.
37-
*/
3834
private final SparseArray<Rect> mHeaderRects = new SparseArray<>();
39-
private final Rect mTempRect = new Rect();
4035

4136
public StickyRecyclerHeadersDecoration(StickyRecyclerHeadersAdapter adapter) {
4237
this(adapter, new LayoutManagerOrientationProvider(), new DimensionCalculator(), true);
@@ -87,18 +82,20 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle
8782
* @param orientation used to calculate offset for the item
8883
*/
8984
private void setItemOffsetsForHeader(Rect itemOffsets, View header, int orientation) {
90-
mDimensionCalculator.initMargins(mTempRect, header);
85+
Rect headerMargins = mDimensionCalculator.getMargins(header);
9186
if (orientation == LinearLayoutManager.VERTICAL) {
92-
itemOffsets.top = header.getHeight() + mTempRect.top + mTempRect.bottom;
87+
itemOffsets.top = header.getHeight() + headerMargins.top + headerMargins.bottom;
9388
} else {
94-
itemOffsets.left = header.getWidth() + mTempRect.left + mTempRect.right;
89+
itemOffsets.left = header.getWidth() + headerMargins.left + headerMargins.right;
9590
}
9691
}
9792

9893
@Override
9994
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
10095
super.onDrawOver(canvas, parent, state);
10196

97+
mHeaderRects.clear();
98+
10299
final int childCount = parent.getChildCount();
103100
if (childCount <= 0 || mAdapter.getItemCount() <= 0) {
104101
return;
@@ -117,15 +114,11 @@ public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State st
117114
if (hasStickyHeader || hasNewHeader) {
118115
View header = mHeaderProvider.getHeader(parent, position);
119116

120-
//re-use existing Rect, if any.
121-
Rect headerOffset = mHeaderRects.get(position);
122-
if (headerOffset == null) {
123-
mHeaderRects.put(position, headerOffset = new Rect());
124-
}
125-
126-
mHeaderPositionCalculator.initHeaderBounds(headerOffset, parent, header, itemView, hasStickyHeader, mEnableStickyHeader);
117+
Rect headerOffset = mHeaderPositionCalculator.getHeaderBounds(parent, header, itemView, hasStickyHeader, mEnableStickyHeader);
127118
mRenderer.drawHeader(parent, canvas, header, headerOffset);
128119

120+
mHeaderRects.put(position, headerOffset);
121+
129122
if (mEnableStickyHeader && mHeaderListener != null) {
130123
mHeaderListener.onHeaderPositionChanged(this, mAdapter.getHeaderId(position), header, position, headerOffset);
131124
}

0 commit comments

Comments
 (0)