1616
1717package com .google .android .material .carousel ;
1818
19+ import static java .lang .Math .max ;
20+ import static java .lang .Math .min ;
21+
1922import androidx .annotation .NonNull ;
2023import androidx .core .math .MathUtils ;
2124import com .google .android .material .animation .AnimationUtils ;
@@ -82,11 +85,17 @@ private KeylineStateList(
8285 }
8386
8487 /** Creates a new {@link KeylineStateList} from a {@link KeylineState}. */
85- static KeylineStateList from (Carousel carousel , KeylineState state , float itemMargins ,
86- float leftOrTopPadding , float rightOrBottomPadding ) {
88+ static KeylineStateList from (
89+ Carousel carousel ,
90+ KeylineState state ,
91+ float itemMargins ,
92+ float leftOrTopPadding ,
93+ float rightOrBottomPadding ,
94+ CarouselStrategy .StrategyType strategyType ) {
8795 return new KeylineStateList (
88- state , getStateStepsStart (carousel , state , itemMargins , leftOrTopPadding ),
89- getStateStepsEnd (carousel , state , itemMargins , rightOrBottomPadding ));
96+ state ,
97+ getStateStepsStart (carousel , state , itemMargins , leftOrTopPadding , strategyType ),
98+ getStateStepsEnd (carousel , state , itemMargins , rightOrBottomPadding , strategyType ));
9099 }
91100
92101 /** Returns the default state for this state list. */
@@ -155,14 +164,17 @@ KeylineState getShiftedState(
155164 float startShiftOffset = minScrollOffset + startShiftRange ;
156165 float endShiftOffset = maxScrollOffset - endShiftRange ;
157166 float startPaddingShift = getStartState ().getFirstFocalKeyline ().leftOrTopPaddingShift ;
158- float endPaddingShift = getEndState ().getLastFocalKeyline ().rightOrBottomPaddingShift ;
167+ float endPaddingShift = getEndState ().getFirstFocalKeyline ().rightOrBottomPaddingShift ;
159168
160169 // Normally we calculate the interpolation such that by scrollShiftOffset, we are always at the
161- // default state but in this case, we want to start shifting earlier/increase startShiftOffset
162- // so that the interpolation will choose the start state instead of the default state when the
163- // scroll offset is equal to startPaddingShift. This is so we are always at the start state
164- // at the beginning of the carousel, instead of getting to a state where the start state is only
170+ // default state. In the case where the start state is equal to the default state but with
171+ // padding, we want to start shifting earlier/increase startShiftOffset so that the
172+ // interpolation will choose the start state instead of the default state when the scroll offset
173+ // is equal to startPaddingShift. This is so we are always at the start state with padding at
174+ // the beginning of the carousel, instead of getting to a state where the start state is only
165175 // when scrollOffset <= startPaddingShift.
176+ // We know that the start state is equal to the default state with padding if the start shift
177+ // range is equal to the padding.
166178 if (startShiftRange == startPaddingShift ) {
167179 startShiftOffset += startPaddingShift ;
168180 }
@@ -362,7 +374,56 @@ private static boolean isLastFocalItemVisibleAtRightOfContainer(
362374 && state .getLastFocalKeyline () == state .getLastNonAnchorKeyline ();
363375 }
364376
377+ @ NonNull
365378 private static KeylineState shiftKeylineStateForPadding (
379+ @ NonNull KeylineState keylineState , float padding , float carouselSize , boolean leftShift ,
380+ float childMargins , CarouselStrategy .StrategyType strategyType ) {
381+ switch (strategyType ) {
382+ case CONTAINED :
383+ return shiftKeylineStateForPaddingContained (
384+ keylineState , padding , carouselSize , leftShift , childMargins );
385+ default :
386+ return shiftKeylineStateForPaddingUncontained (
387+ keylineState , padding , carouselSize , leftShift );
388+ }
389+ }
390+
391+ @ NonNull
392+ private static KeylineState shiftKeylineStateForPaddingUncontained (
393+ @ NonNull KeylineState keylineState , float padding , float carouselSize , boolean leftShift ) {
394+ List <Keyline > tmpKeylines = new ArrayList <>(keylineState .getKeylines ());
395+ KeylineState .Builder builder =
396+ new KeylineState .Builder (keylineState .getItemSize (), carouselSize );
397+ int unchangingAnchorPosition = leftShift ? 0 : tmpKeylines .size () - 1 ;
398+ for (int j = 0 ; j < tmpKeylines .size (); j ++) {
399+ Keyline k = tmpKeylines .get (j );
400+ if (k .isAnchor && j == unchangingAnchorPosition ) {
401+ builder .addKeyline (k .locOffset , k .mask , k .maskedItemSize , false , true , k .cutoff );
402+ continue ;
403+ }
404+ float newOffset = leftShift ? k .locOffset + padding : k .locOffset - padding ;
405+ float leftOrTopPadding = leftShift ? padding : 0 ;
406+ float rightOrBottomPadding = leftShift ? 0 : padding ;
407+ boolean isFocal =
408+ j >= keylineState .getFirstFocalKeylineIndex ()
409+ && j <= keylineState .getLastFocalKeylineIndex ();
410+ builder .addKeyline (
411+ newOffset ,
412+ k .mask ,
413+ k .maskedItemSize ,
414+ isFocal ,
415+ k .isAnchor ,
416+ Math .abs (
417+ leftShift
418+ ? max (0 , newOffset + k .maskedItemSize / 2 - carouselSize )
419+ : min (0 , newOffset - k .maskedItemSize / 2 )),
420+ leftOrTopPadding ,
421+ rightOrBottomPadding );
422+ }
423+ return builder .build ();
424+ }
425+
426+ private static KeylineState shiftKeylineStateForPaddingContained (
366427 KeylineState keylineState , float padding , float carouselSize , boolean leftShift ,
367428 float childMargins ) {
368429
@@ -400,7 +461,7 @@ private static KeylineState shiftKeylineStateForPadding(
400461 maskedItemSize , keylineState .getItemSize (), childMargins );
401462 float locOffset = nextOffset + maskedItemSize / 2F ;
402463
403- float actualPaddingShift = locOffset - k .locOffset ;
464+ float actualPaddingShift = Math . abs ( locOffset - k .locOffset ) ;
404465
405466 builder .addKeyline (
406467 locOffset ,
@@ -434,7 +495,7 @@ private static KeylineState shiftKeylineStateForPadding(
434495 */
435496 private static List <KeylineState > getStateStepsStart (
436497 Carousel carousel , KeylineState defaultState , float itemMargins ,
437- float leftOrTopPaddingForKeylineShift ) {
498+ float leftOrTopPaddingForKeylineShift , CarouselStrategy . StrategyType strategyType ) {
438499 List <KeylineState > steps = new ArrayList <>();
439500 steps .add (defaultState );
440501 int firstNonAnchorKeylineIndex = findFirstNonAnchorKeylineIndex (defaultState );
@@ -453,7 +514,8 @@ private static List<KeylineState> getStateStepsStart(
453514 leftOrTopPaddingForKeylineShift ,
454515 carouselSize ,
455516 true ,
456- itemMargins ));
517+ itemMargins ,
518+ strategyType ));
457519 }
458520 return steps ;
459521 }
@@ -471,8 +533,10 @@ private static List<KeylineState> getStateStepsStart(
471533 // view.
472534 float cutoffs = defaultState .getFirstFocalKeyline ().cutoff ;
473535 steps .add (
474- shiftKeylinesAndCreateKeylineState (defaultState , originalStart + cutoffs , carouselSize ));
475- // TODO(b/316968490): If there is padding, this should affect keylines and the cutoffs
536+ shiftKeylinesAndCreateKeylineState (
537+ defaultState ,
538+ originalStart + cutoffs + leftOrTopPaddingForKeylineShift ,
539+ carouselSize ));
476540 return steps ;
477541 }
478542
@@ -512,7 +576,8 @@ private static List<KeylineState> getStateStepsStart(
512576 leftOrTopPaddingForKeylineShift ,
513577 carouselSize ,
514578 true ,
515- itemMargins );
579+ itemMargins ,
580+ strategyType );
516581 }
517582 steps .add (shifted );
518583 }
@@ -536,7 +601,8 @@ private static List<KeylineState> getStateStepsStart(
536601 * carousel.
537602 */
538603 private static List <KeylineState > getStateStepsEnd (Carousel carousel , KeylineState defaultState ,
539- float itemMargins , float rightOrBottomPaddingForKeylineShift ) {
604+ float itemMargins , float rightOrBottomPaddingForKeylineShift ,
605+ CarouselStrategy .StrategyType strategyType ) {
540606 List <KeylineState > steps = new ArrayList <>();
541607 steps .add (defaultState );
542608 int lastNonAnchorKeylineIndex = findLastNonAnchorKeylineIndex (defaultState );
@@ -556,7 +622,8 @@ private static List<KeylineState> getStateStepsEnd(Carousel carousel, KeylineSta
556622 rightOrBottomPaddingForKeylineShift ,
557623 carouselSize ,
558624 false ,
559- itemMargins ));
625+ itemMargins ,
626+ strategyType ));
560627 }
561628 return steps ;
562629 }
@@ -573,9 +640,8 @@ private static List<KeylineState> getStateStepsEnd(Carousel carousel, KeylineSta
573640 // view. Add a step that shifts all the keylines over to bring the last focal item into full
574641 // view.
575642 float cutoffs = defaultState .getLastFocalKeyline ().cutoff ;
576- steps .add (
577- shiftKeylinesAndCreateKeylineState (defaultState , originalStart - cutoffs , carouselSize ));
578- // TODO(b/316968490): If there is padding, this should affect keylines and the cutoffs
643+ steps .add (shiftKeylinesAndCreateKeylineState (defaultState ,
644+ originalStart - cutoffs - rightOrBottomPaddingForKeylineShift , carouselSize ));
579645 return steps ;
580646 }
581647
@@ -614,7 +680,8 @@ private static List<KeylineState> getStateStepsEnd(Carousel carousel, KeylineSta
614680 rightOrBottomPaddingForKeylineShift ,
615681 carouselSize ,
616682 false ,
617- itemMargins );
683+ itemMargins ,
684+ strategyType );
618685 }
619686 steps .add (shifted );
620687 }
0 commit comments