40
40
import android .util .AttributeSet ;
41
41
import android .util .Log ;
42
42
import android .view .View ;
43
+ import android .view .View .OnLayoutChangeListener ;
43
44
import android .view .ViewGroup ;
44
45
import android .view .accessibility .AccessibilityEvent ;
45
46
import androidx .annotation .IntDef ;
@@ -115,14 +116,18 @@ public class CarouselLayoutManager extends LayoutManager
115
116
116
117
private CarouselOrientationHelper orientationHelper ;
117
118
118
- /**
119
- * Aligns large items to the start of the carousel.
120
- */
119
+ private final OnLayoutChangeListener recyclerViewSizeChangeListener =
120
+ (v , left , top , right , bottom , oldLeft , oldTop , oldRight , oldBottom ) -> {
121
+ // If RV dimen values have changed, refresh the keyline state.
122
+ if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom ) {
123
+ v .post (this ::refreshKeylineState );
124
+ }
125
+ };
126
+
127
+ /** Aligns large items to the start of the carousel. */
121
128
public static final int ALIGNMENT_START = 0 ;
122
129
123
- /**
124
- * Aligns large items to the center of the carousel.
125
- */
130
+ /** Aligns large items to the center of the carousel. */
126
131
public static final int ALIGNMENT_CENTER = 1 ;
127
132
128
133
/**
@@ -226,9 +231,14 @@ public void setCarouselStrategy(@NonNull CarouselStrategy carouselStrategy) {
226
231
@ Override
227
232
public void onAttachedToWindow (RecyclerView view ) {
228
233
super .onAttachedToWindow (view );
229
- // TODO: b/291123558 - Keylines should be also be refreshed when
230
- // there's any change affecting dimens of the RecyclerView.
231
234
refreshKeylineState ();
235
+ view .addOnLayoutChangeListener (recyclerViewSizeChangeListener );
236
+ }
237
+
238
+ @ Override
239
+ public void onDetachedFromWindow (RecyclerView view , Recycler recycler ) {
240
+ super .onDetachedFromWindow (view , recycler );
241
+ view .removeOnLayoutChangeListener (recyclerViewSizeChangeListener );
232
242
}
233
243
234
244
@ Override
@@ -241,16 +251,11 @@ public void onLayoutChildren(Recycler recycler, State state) {
241
251
242
252
boolean isRtl = isLayoutRtl ();
243
253
254
+ boolean isInitialLoad = keylineStateList == null ;
244
255
// If a keyline state hasn't been created, use the first child as a representative of how each
245
256
// child would like to be measured and allow the strategy to create a keyline state.
246
- boolean isInitialLoad = keylineStateList == null ;
247
257
if (isInitialLoad ) {
248
- View firstChild = recycler .getViewForPosition (0 );
249
- measureChildWithMargins (firstChild , 0 , 0 );
250
- KeylineState keylineState =
251
- carouselStrategy .onFirstChildMeasuredWithMargins (this , firstChild );
252
- keylineStateList =
253
- KeylineStateList .from (this , isRtl ? KeylineState .reverse (keylineState ) : keylineState );
258
+ recalculateKeylineStateList (recycler );
254
259
}
255
260
256
261
// Ensure our scroll limits are initialized and valid for the data set size.
@@ -277,12 +282,21 @@ public void onLayoutChildren(Recycler recycler, State state) {
277
282
// Ensure currentFillStartPosition is valid if the number of items in the adapter has changed.
278
283
currentFillStartPosition = MathUtils .clamp (currentFillStartPosition , 0 , state .getItemCount ());
279
284
280
- updateCurrentKeylineStateForScrollOffset ();
285
+ updateCurrentKeylineStateForScrollOffset (keylineStateList );
281
286
282
287
detachAndScrapAttachedViews (recycler );
283
288
fill (recycler , state );
284
289
}
285
290
291
+ private void recalculateKeylineStateList (Recycler recycler ) {
292
+ View firstChild = recycler .getViewForPosition (0 );
293
+ measureChildWithMargins (firstChild , 0 , 0 );
294
+ KeylineState keylineState = carouselStrategy .onFirstChildMeasuredWithMargins (this , firstChild );
295
+ keylineStateList =
296
+ KeylineStateList .from (
297
+ this , isLayoutRtl () ? KeylineState .reverse (keylineState ) : keylineState );
298
+ }
299
+
286
300
/**
287
301
* Recalculates the {@link KeylineState} and {@link KeylineStateList} for the current strategy.
288
302
*/
@@ -683,7 +697,8 @@ private static KeylineRange getSurroundingKeylineRange(
683
697
*
684
698
* <p>This method should be called any time a change in the scroll offset occurs.
685
699
*/
686
- private void updateCurrentKeylineStateForScrollOffset () {
700
+ private void updateCurrentKeylineStateForScrollOffset (
701
+ @ NonNull KeylineStateList keylineStateList ) {
687
702
if (maxScroll <= minScroll ) {
688
703
// We don't have enough items in the list to scroll and we should use the keyline state
689
704
// that aligns items to the start of the container.
@@ -697,9 +712,9 @@ private void updateCurrentKeylineStateForScrollOffset() {
697
712
}
698
713
699
714
/**
700
- * Calculates the horizontal distance children should be scrolled by for a given {@code dx }.
715
+ * Calculates the distance children should be scrolled by for a given {@code delta }.
701
716
*
702
- * @param dx the delta, resulting from a gesture or other event, that the list would like to be
717
+ * @param delta the delta, resulting from a gesture or other event, that the list would like to be
703
718
* scrolled by
704
719
* @param currentScroll the current horizontal scroll offset that is always between the min and
705
720
* max horizontal scroll
@@ -724,7 +739,7 @@ private static int calculateShouldScrollBy(
724
739
* Calculates the total offset needed to scroll the first item in the list to the center of the
725
740
* start focal keyline.
726
741
*/
727
- private int calculateStartScroll (KeylineStateList stateList ) {
742
+ private int calculateStartScroll (@ NonNull KeylineStateList stateList ) {
728
743
boolean isRtl = isLayoutRtl ();
729
744
KeylineState startState = isRtl ? stateList .getEndState () : stateList .getStartState ();
730
745
Keyline startFocalKeyline =
@@ -1106,7 +1121,7 @@ public void scrollToPosition(int position) {
1106
1121
}
1107
1122
scrollOffset = getScrollOffsetForPosition (position , getKeylineStateForPosition (position ));
1108
1123
currentFillStartPosition = MathUtils .clamp (position , 0 , max (0 , getItemCount () - 1 ));
1109
- updateCurrentKeylineStateForScrollOffset ();
1124
+ updateCurrentKeylineStateForScrollOffset (keylineStateList );
1110
1125
requestLayout ();
1111
1126
}
1112
1127
@@ -1211,10 +1226,14 @@ private int scrollBy(int distance, Recycler recycler, State state) {
1211
1226
return 0 ;
1212
1227
}
1213
1228
1229
+ if (keylineStateList == null ) {
1230
+ recalculateKeylineStateList (recycler );
1231
+ }
1232
+
1214
1233
// Calculate how much the carousel should scroll and update the scroll offset.
1215
1234
int scrolledBy = calculateShouldScrollBy (distance , scrollOffset , minScroll , maxScroll );
1216
1235
scrollOffset += scrolledBy ;
1217
- updateCurrentKeylineStateForScrollOffset ();
1236
+ updateCurrentKeylineStateForScrollOffset (keylineStateList );
1218
1237
1219
1238
float halfItemSize = currentKeylineState .getItemSize () / 2F ;
1220
1239
int startPosition = getPosition (getChildAt (0 ));
@@ -1278,7 +1297,7 @@ public int computeHorizontalScrollOffset(@NonNull State state) {
1278
1297
*/
1279
1298
@ Override
1280
1299
public int computeHorizontalScrollExtent (@ NonNull State state ) {
1281
- return (int ) keylineStateList .getDefaultState ().getItemSize ();
1300
+ return keylineStateList == null ? 0 : (int ) keylineStateList .getDefaultState ().getItemSize ();
1282
1301
}
1283
1302
1284
1303
/**
@@ -1300,7 +1319,7 @@ public int computeVerticalScrollOffset(@NonNull State state) {
1300
1319
1301
1320
@ Override
1302
1321
public int computeVerticalScrollExtent (@ NonNull State state ) {
1303
- return (int ) keylineStateList .getDefaultState ().getItemSize ();
1322
+ return keylineStateList == null ? 0 : (int ) keylineStateList .getDefaultState ().getItemSize ();
1304
1323
}
1305
1324
1306
1325
@ Override
0 commit comments