@@ -20,6 +20,13 @@ public class HeaderPositionCalculator {
2020 private final HeaderProvider mHeaderProvider ;
2121 private final DimensionCalculator mDimensionCalculator ;
2222
23+ /**
24+ * The following fields are used as buffers for internal calculations. Their sole purpose is to avoid
25+ * allocating new Rect every time we need one.
26+ */
27+ private final Rect mTempRect1 = new Rect ();
28+ private final Rect mTempRect2 = new Rect ();
29+
2330 public HeaderPositionCalculator (StickyRecyclerHeadersAdapter adapter , HeaderProvider headerProvider ,
2431 OrientationProvider orientationProvider , DimensionCalculator dimensionCalculator ) {
2532 mAdapter = adapter ;
@@ -41,12 +48,13 @@ public HeaderPositionCalculator(StickyRecyclerHeadersAdapter adapter, HeaderProv
4148 */
4249 public boolean hasStickyHeader (View itemView , int orientation , int position ) {
4350 int offset , margin ;
51+ mDimensionCalculator .initMargins (mTempRect1 , itemView );
4452 if (orientation == LinearLayout .VERTICAL ) {
4553 offset = itemView .getTop ();
46- margin = mDimensionCalculator . getMargins ( itemView ) .top ;
54+ margin = mTempRect1 .top ;
4755 } else {
4856 offset = itemView .getLeft ();
49- margin = mDimensionCalculator . getMargins ( itemView ) .left ;
57+ margin = mTempRect1 .left ;
5058 }
5159
5260 return offset <= margin && mAdapter .getHeaderId (position ) >= 0 ;
@@ -56,11 +64,12 @@ public boolean hasStickyHeader(View itemView, int orientation, int position) {
5664 * Determines if an item in the list should have a header that is different than the item in the
5765 * list that immediately precedes it. Items with no headers will always return false.
5866 *
59- * @param position of the list item in questions
67+ * @param position of the list item in questions
68+ * @param isReverseLayout TRUE if layout manager has flag isReverseLayout
6069 * @return true if this item has a different header than the previous item in the list
6170 * @see {@link StickyRecyclerHeadersAdapter#getHeaderId(int)}
6271 */
63- public boolean hasNewHeader (int position ) {
72+ public boolean hasNewHeader (int position , boolean isReverseLayout ) {
6473 if (indexOutOfBounds (position )) {
6574 return false ;
6675 }
@@ -71,28 +80,20 @@ public boolean hasNewHeader(int position) {
7180 return false ;
7281 }
7382
74- return position == 0 || headerId != mAdapter .getHeaderId (position - 1 );
83+ long nextItemHeaderId = -1 ;
84+ int nextItemPosition = position + (isReverseLayout ? 1 : -1 );
85+ if (!indexOutOfBounds (nextItemPosition )) {
86+ nextItemHeaderId = mAdapter .getHeaderId (nextItemPosition );
87+ }
88+ int firstItemPosition = isReverseLayout ? mAdapter .getItemCount () - 1 : 0 ;
89+
90+ return position == firstItemPosition || headerId != nextItemHeaderId ;
7591 }
7692
7793 private boolean indexOutOfBounds (int position ) {
7894 return position < 0 || position >= mAdapter .getItemCount ();
7995 }
8096
81- public Rect getHeaderBounds (RecyclerView recyclerView , View header , View firstView , boolean firstHeader ) {
82- int orientation = mOrientationProvider .getOrientation (recyclerView );
83- Rect bounds = getDefaultHeaderOffset (recyclerView , header , firstView , orientation );
84-
85- if (firstHeader && isStickyHeaderBeingPushedOffscreen (recyclerView , header )) {
86- View viewAfterNextHeader = getFirstViewUnobscuredByHeader (recyclerView , header );
87- int firstViewUnderHeaderPosition = recyclerView .getChildAdapterPosition (viewAfterNextHeader );
88- View secondHeader = mHeaderProvider .getHeader (recyclerView , firstViewUnderHeaderPosition );
89- translateHeaderWithNextHeader (recyclerView , mOrientationProvider .getOrientation (recyclerView ), bounds ,
90- header , viewAfterNextHeader , secondHeader );
91- }
92-
93- return bounds ;
94- }
95-
9697 /**
9798 * Verify if header obscure some item on RecyclerView
9899 *
@@ -109,22 +110,35 @@ public boolean headerObscuringSomeItem(RecyclerView parent, View firstHeader) {
109110 return false ;
110111 }
111112
112- private Rect getDefaultHeaderOffset (RecyclerView recyclerView , View header , View firstView , int orientation ) {
113+ public void initHeaderBounds (Rect bounds , RecyclerView recyclerView , View header , View firstView , boolean firstHeader ) {
114+ int orientation = mOrientationProvider .getOrientation (recyclerView );
115+ initDefaultHeaderOffset (bounds , recyclerView , header , firstView , orientation );
116+
117+ if (firstHeader && isStickyHeaderBeingPushedOffscreen (recyclerView , header )) {
118+ View viewAfterNextHeader = getFirstViewUnobscuredByHeader (recyclerView , header );
119+ int firstViewUnderHeaderPosition = recyclerView .getChildAdapterPosition (viewAfterNextHeader );
120+ View secondHeader = mHeaderProvider .getHeader (recyclerView , firstViewUnderHeaderPosition );
121+ translateHeaderWithNextHeader (recyclerView , mOrientationProvider .getOrientation (recyclerView ), bounds ,
122+ header , viewAfterNextHeader , secondHeader );
123+ }
124+ }
125+
126+ private void initDefaultHeaderOffset (Rect headerMargins , RecyclerView recyclerView , View header , View firstView , int orientation ) {
113127 int translationX , translationY ;
114- Rect headerMargins = mDimensionCalculator .getMargins ( header );
128+ mDimensionCalculator .initMargins ( mTempRect1 , header );
115129 if (orientation == LinearLayoutManager .VERTICAL ) {
116- translationX = firstView .getLeft () + headerMargins .left ;
130+ translationX = firstView .getLeft () + mTempRect1 .left ;
117131 translationY = Math .max (
118- firstView .getTop () - header .getHeight () - headerMargins .bottom ,
119- getListTop (recyclerView ) + headerMargins .top );
132+ firstView .getTop () - header .getHeight () - mTempRect1 .bottom ,
133+ getListTop (recyclerView ) + mTempRect1 .top );
120134 } else {
121- translationY = firstView .getTop () + headerMargins .top ;
135+ translationY = firstView .getTop () + mTempRect1 .top ;
122136 translationX = Math .max (
123- firstView .getLeft () - header .getWidth () - headerMargins .right ,
124- getListLeft (recyclerView ) + headerMargins .left );
137+ firstView .getLeft () - header .getWidth () - mTempRect1 .right ,
138+ getListLeft (recyclerView ) + mTempRect1 .left );
125139 }
126140
127- return new Rect (translationX , translationY , translationX + header .getWidth (),
141+ headerMargins . set (translationX , translationY , translationX + header .getWidth (),
128142 translationY + header .getHeight ());
129143 }
130144
@@ -135,20 +149,21 @@ private boolean isStickyHeaderBeingPushedOffscreen(RecyclerView recyclerView, Vi
135149 return false ;
136150 }
137151
138- if (firstViewUnderHeaderPosition > 0 && hasNewHeader (firstViewUnderHeaderPosition )) {
152+ boolean isReverseLayout = mOrientationProvider .isReverseLayout (recyclerView );
153+ if (firstViewUnderHeaderPosition > 0 && hasNewHeader (firstViewUnderHeaderPosition , isReverseLayout )) {
139154 View nextHeader = mHeaderProvider .getHeader (recyclerView , firstViewUnderHeaderPosition );
140- Rect nextHeaderMargins = mDimensionCalculator .getMargins ( nextHeader );
141- Rect headerMargins = mDimensionCalculator .getMargins ( stickyHeader );
155+ mDimensionCalculator .initMargins ( mTempRect1 , nextHeader );
156+ mDimensionCalculator .initMargins ( mTempRect2 , stickyHeader );
142157
143158 if (mOrientationProvider .getOrientation (recyclerView ) == LinearLayoutManager .VERTICAL ) {
144- int topOfNextHeader = viewAfterHeader .getTop () - nextHeaderMargins .bottom - nextHeader .getHeight () - nextHeaderMargins .top ;
145- int bottomOfThisHeader = recyclerView .getPaddingTop () + stickyHeader .getBottom () + headerMargins .top + headerMargins .bottom ;
159+ int topOfNextHeader = viewAfterHeader .getTop () - mTempRect1 .bottom - nextHeader .getHeight () - mTempRect1 .top ;
160+ int bottomOfThisHeader = recyclerView .getPaddingTop () + stickyHeader .getBottom () + mTempRect2 .top + mTempRect2 .bottom ;
146161 if (topOfNextHeader < bottomOfThisHeader ) {
147162 return true ;
148163 }
149164 } else {
150- int leftOfNextHeader = viewAfterHeader .getLeft () - nextHeaderMargins .right - nextHeader .getWidth () - nextHeaderMargins .left ;
151- int rightOfThisHeader = recyclerView .getPaddingLeft () + stickyHeader .getRight () + headerMargins .left + headerMargins .right ;
165+ int leftOfNextHeader = viewAfterHeader .getLeft () - mTempRect1 .right - nextHeader .getWidth () - mTempRect1 .left ;
166+ int rightOfThisHeader = recyclerView .getPaddingLeft () + stickyHeader .getRight () + mTempRect2 .left + mTempRect2 .right ;
152167 if (leftOfNextHeader < rightOfThisHeader ) {
153168 return true ;
154169 }
@@ -160,17 +175,17 @@ private boolean isStickyHeaderBeingPushedOffscreen(RecyclerView recyclerView, Vi
160175
161176 private void translateHeaderWithNextHeader (RecyclerView recyclerView , int orientation , Rect translation ,
162177 View currentHeader , View viewAfterNextHeader , View nextHeader ) {
163- Rect nextHeaderMargins = mDimensionCalculator .getMargins ( nextHeader );
164- Rect stickyHeaderMargins = mDimensionCalculator .getMargins ( currentHeader );
178+ mDimensionCalculator .initMargins ( mTempRect1 , nextHeader );
179+ mDimensionCalculator .initMargins ( mTempRect2 , currentHeader );
165180 if (orientation == LinearLayoutManager .VERTICAL ) {
166- int topOfStickyHeader = getListTop (recyclerView ) + stickyHeaderMargins .top + stickyHeaderMargins .bottom ;
167- int shiftFromNextHeader = viewAfterNextHeader .getTop () - nextHeader .getHeight () - nextHeaderMargins .bottom - nextHeaderMargins .top - currentHeader .getHeight () - topOfStickyHeader ;
181+ int topOfStickyHeader = getListTop (recyclerView ) + mTempRect2 .top + mTempRect2 .bottom ;
182+ int shiftFromNextHeader = viewAfterNextHeader .getTop () - nextHeader .getHeight () - mTempRect1 .bottom - mTempRect1 .top - currentHeader .getHeight () - topOfStickyHeader ;
168183 if (shiftFromNextHeader < topOfStickyHeader ) {
169184 translation .top += shiftFromNextHeader ;
170185 }
171186 } else {
172- int leftOfStickyHeader = getListLeft (recyclerView ) + stickyHeaderMargins .left + stickyHeaderMargins .right ;
173- int shiftFromNextHeader = viewAfterNextHeader .getLeft () - nextHeader .getWidth () - nextHeaderMargins .right - nextHeaderMargins .left - currentHeader .getWidth () - leftOfStickyHeader ;
187+ int leftOfStickyHeader = getListLeft (recyclerView ) + mTempRect2 .left + mTempRect2 .right ;
188+ int shiftFromNextHeader = viewAfterNextHeader .getLeft () - nextHeader .getWidth () - mTempRect1 .right - mTempRect1 .left - currentHeader .getWidth () - leftOfStickyHeader ;
174189 if (shiftFromNextHeader < leftOfStickyHeader ) {
175190 translation .left += shiftFromNextHeader ;
176191 }
@@ -180,11 +195,14 @@ private void translateHeaderWithNextHeader(RecyclerView recyclerView, int orient
180195 /**
181196 * Returns the first item currently in the RecyclerView that is not obscured by a header.
182197 *
183- * @param parent RecyclerView containing all the list items
198+ * @param parent Recyclerview containing all the list items
184199 * @return first item that is fully beneath a header
185200 */
186201 private View getFirstViewUnobscuredByHeader (RecyclerView parent , View firstHeader ) {
187- for (int i = 0 ; i < parent .getChildCount (); i ++) {
202+ boolean isReverseLayout = mOrientationProvider .isReverseLayout (parent );
203+ int step = isReverseLayout ? -1 : 1 ;
204+ int from = isReverseLayout ? parent .getChildCount () - 1 : 0 ;
205+ for (int i = from ; i >= 0 && i <= parent .getChildCount () - 1 ; i += step ) {
188206 View child = parent .getChildAt (i );
189207 if (!itemIsObscuredByHeader (parent , child , firstHeader , mOrientationProvider .getOrientation (parent ))) {
190208 return child ;
@@ -204,7 +222,7 @@ private View getFirstViewUnobscuredByHeader(RecyclerView parent, View firstHeade
204222 */
205223 private boolean itemIsObscuredByHeader (RecyclerView parent , View item , View header , int orientation ) {
206224 RecyclerView .LayoutParams layoutParams = (RecyclerView .LayoutParams ) item .getLayoutParams ();
207- Rect headerMargins = mDimensionCalculator .getMargins ( header );
225+ mDimensionCalculator .initMargins ( mTempRect1 , header );
208226
209227 int adapterPosition = parent .getChildAdapterPosition (item );
210228 if (adapterPosition == RecyclerView .NO_POSITION || mHeaderProvider .getHeader (parent , adapterPosition ) != header ) {
@@ -215,13 +233,13 @@ private boolean itemIsObscuredByHeader(RecyclerView parent, View item, View head
215233
216234 if (orientation == LinearLayoutManager .VERTICAL ) {
217235 int itemTop = item .getTop () - layoutParams .topMargin ;
218- int headerBottom = header .getBottom () + headerMargins .bottom + headerMargins .top ;
236+ int headerBottom = header .getBottom () + mTempRect1 .bottom + mTempRect1 .top ;
219237 if (itemTop >= headerBottom ) {
220238 return false ;
221239 }
222240 } else {
223241 int itemLeft = item .getLeft () - layoutParams .leftMargin ;
224- int headerRight = header .getRight () + headerMargins .right + headerMargins .left ;
242+ int headerRight = header .getRight () + mTempRect1 .right + mTempRect1 .left ;
225243 if (itemLeft >= headerRight ) {
226244 return false ;
227245 }
0 commit comments